Refactor work

This commit is contained in:
jtm
2012-02-25 17:08:08 +00:00
parent f4be7a331d
commit e08bef4d0e
14 changed files with 406 additions and 67 deletions

View File

@@ -7,6 +7,35 @@
"""
class Formatter(object):
def format(self, parse, minify=False, xminify=False):
"""
"""
eb = '\n'
if xminify:
eb = ''
minify = True
self.minify = minify
self.items = {}
if minify:
self.items.update({
'nl': '',
'tab': '',
'ws': '',
'endblock': eb
})
else:
self.items.update({
'nl': '\n',
'tab': '\t',
'ws': ' ',
'endblock': eb
})
self.out = [u.format(self.items)
for u in parse.result
if u]
return ''.join(self.out).strip()
def xformat(self, parse, minify=False, xminify=False):
""" Format css output from parser
@param Parse-result object: Parse-result object
@param bool: Minify flag

View File

@@ -76,12 +76,27 @@ class LessParser(object):
self.scope.push()
self.target = filename
self.result = self.parser.parse(filename, lexer=self.lex, debug=debuglevel)
# [print(r) for r in self.result]
def scopemap(self):
""" Output scopemap.
"""
if self.result:
utility.print_recursive(self.result)
# utility.print_recursive(self.result)
self._scopemap_aux(self.result)
def _scopemap_aux(self, ll, lvl=0):
pad = ''.join(['\t.'] * lvl)
t = type(ll)
if t is list:
for p in ll:
self._scopemap_aux(p, lvl)
elif hasattr(ll, 'tokens'):
print(pad, t)
self._scopemap_aux(list(utility.flatten(ll.tokens)), lvl+1)
# else:
# print(pad, t)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -98,6 +113,8 @@ class LessParser(object):
"""
if len(p) == 3:
p[1].append(p[2])
else:
p[1] = [p[1]]
p[0] = p[1]
def p_unit(self, p):
@@ -106,7 +123,7 @@ class LessParser(object):
| block_decl
| mixin_decl
"""
p[0] = [p[1]]
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -181,12 +198,15 @@ class LessParser(object):
def p_media_open(self, p):
""" block_open : css_media t_ws identifier brace_open
"""
p[0] = [p[1], p[3]]
ident = [p[1], p[2]]
ident.extend(p[3].tokens)
p[3].tokens = ident
p[0] = p[3]
def p_font_face_open(self, p):
""" block_open : css_font_face t_ws brace_open
"""
p[0] = p[1]
p[0] = Identifier([p[1], p[2]])
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -258,12 +278,12 @@ class LessParser(object):
""" variable_decl : variable ':' style_list ';'
"""
try:
v = Variable(p)
# v.parse(self.scope)
# if self.scope.current == '__mixin__':
# self.stash[v.name()] = v
# else:
# self.scope.add_variable(v)
v = Variable(list(p)[1:])
v.parse(self.scope)
if self.scope.in_mixin():
self.stash[v.name] = v
else:
self.scope.add_variable(v)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
@@ -278,12 +298,6 @@ class LessParser(object):
| prop_open less_arguments ';'
"""
p[0] = Property(list(p)[1:-1])
if not self.scope.in_mixin():
try:
p[0].parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_prop_open(self, p):
""" prop_open : property ':'

View File

@@ -36,7 +36,7 @@ class Scope(list):
def add_variable(self, variable):
"""
"""
self[-1]['__variables__'][variable.name()] = variable
self[-1]['__variables__'][variable.name] = variable
def variables(self, name):
"""

View File

@@ -6,6 +6,7 @@
<jtm@robot.is>
"""
import collections
import re
def flatten(l):
"""
@@ -69,41 +70,36 @@ def destring(v):
def analyze_number(var, err=''):
""" Analyse number for type and split from unit
@param str: value
@raises: SyntaxError
@return: tuple (number, unit)
"""
u = None
n, u = split_unit(var)
if type(var) is not str:
return (var, u)
if is_color(var):
return (var, 'color')
var = var.strip()
n = var
if not '.' in var:
try:
n = int(var)
return (n, u)
except (ValueError, TypeError):
pass
try:
n = float(var)
except (ValueError, TypeError):
try:
n = ''.join([c for c in var if c in '0123456789.-'])
n = float(n) if '.' in n else int(n)
u = ''.join([c for c in var if c not in '0123456789.-'])
except ValueError:
raise SyntaxError('%s ´%s´' % (err, var))
if is_int(n):
n = int(n)
elif is_float(n):
n = float(n)
else:
raise SyntaxError('%s ´%s´' % (err, var))
return (n, u)
def with_unit(n, u):
def with_unit(n, u=None):
""" Return number with unit
@param int/float: value
@param str: unit
@return: mixed
"""
if type(n) is tuple:
n, u = n
if n == 0: return 0
if u:
return "%s%s" % (str(n), u)
n = str(n)
if n.startswith('.'):
n = '0' + n
return "%s%s" % (n, u)
return n
def is_color(v):
@@ -130,24 +126,32 @@ def is_variable(v):
return (v.startswith('@') or v.startswith('-@'))
return False
def print_recursive(ll, lvl=0):
""" Scopemap printer
@param list: parser result
@param int: depth
def is_int(v):
"""
pad = ''.join(['.'] * lvl)
t = type(ll)
if t is list:
for l in ll: print_recursive(l, lvl+1)
elif hasattr(ll, '_p'):
print(pad, type(ll))
print(pad, '[')
print_recursive(list(flatten(ll._p)), lvl+1)
print(pad, ']')
elif t is str:
print("%s '%s'" % (pad, ll))
else:
print("%s %s" % (pad, ll))
"""
try:
int(str(v))
return True
except (ValueError, TypeError):
pass
return False
def is_float(v):
"""
"""
if not is_int(v):
try:
float(str(v))
return True
except (ValueError, TypeError):
pass
return False
def split_unit(v):
"""
"""
r = re.search('^(\-?[\d\.]+)(.*)$', str(v))
return r.groups() if r else ('','')

View File

@@ -4,6 +4,7 @@ __all__ = [
'Expression',
'Identifier',
'Mixin',
'Node',
'Property',
'Statement',
'String',
@@ -14,6 +15,7 @@ from .call import Call
from .expression import Expression
from .identifier import Identifier
from .mixin import Mixin
from .node import Node
from .property import Property
from .statement import Statement
from .string import String

View File

@@ -3,5 +3,26 @@
from .node import Node
from lesscpy.lessc import utility
class Block(Node):
pass
def parse(self, scope):
pass
ident, inner = self.tokens
self.name = ident.parse(scope)
self.parsed = [p.parse(scope)
for p in inner
if p and type(p) is not type(self)]
self.inner = [p.parse(scope)
for p in inner
if p and type(p) is type(self)]
return self
def format(self, fills):
"""
"""
f = "%(identifier)s%(ws)s{%(nl)s%(proplist)s}%(nl)s%(endblock)s"
fills.update({
'identifier': self.name,
'proplist': ''.join([p.format(fills) for p in self.parsed]),
'endblock': ''.join([p.format(fills) for p in self.inner]),
})
return f % fills

View File

@@ -1,5 +1,17 @@
"""
"""
from .node import Node
import lesscpy.lessc.utility as utility
class Call(Node):
pass
def parse(self, scope):
self.parsed = [p.parse(scope)
if hasattr(p, 'parse')
else p
for p in utility.flatten(self.tokens)]
return self
def format(self, fills):
return ''.join([p.format(fills)
if hasattr(p, 'format')
else p
for p in self.parsed])

View File

@@ -1,5 +1,70 @@
"""
"""
from .node import Node
from lesscpy.lessc import utility
class Expression(Node):
pass
def parse(self, scope):
""" Parse Node
@param list: current scope
"""
assert(len(self.tokens) == 3)
expr = [t.parse(scope) if hasattr(t, 'parse')
else t
for t in self.tokens]
expr = [self.neg(t, scope) for t in expr]
A, O, B = [e[0]
if type(e) is tuple
else e
for e in expr]
a, ua = utility.analyze_number(A, 'Illegal element in expression')
b, ub = utility.analyze_number(B, 'Illegal element in expression')
if(a is False or b is False):
return ''.join([A, O, B])
if ua == 'color' or ub == 'color':
return color.LessColor().process(expression)
out = self.operate(a, b, O)
if type(a) is int and type(b) is int:
out = int(out)
return self.with_units(out, ua, ub)
def neg(self, t, scope):
"""
"""
if t and type(t) is list and t[0] == '-':
v = t[1]
if len(t) > 1 and hasattr(t[1], 'parse'):
v = t[1].parse(scope)
if type(v) is str:
return '-' + v
return -v
return t
def with_units(self, v, ua, ub):
"""
"""
if v == 0: return v;
if ua and ub:
if ua == ub:
return str(v) + ua
else:
raise SyntaxError("Error in expression %s != %s" % (ua, ub))
elif ua:
return str(v) + ua
elif ub:
return str(v) + ub
return v
def operate(self, a, b, o):
"""
"""
operation = {
'+': '__add__',
'-': '__sub__',
'*': '__mul__',
'/': '__truediv__'
}.get(o[0])
v = getattr(a, operation)(b)
if v is NotImplemented:
v = getattr(b, operation)(a)
return v

View File

@@ -1,5 +1,9 @@
"""
"""
from .node import Node
from lesscpy.lessc import utility
class Identifier(Node):
pass
def parse(self, scope):
"""
"""
return ''.join(utility.flatten(self.tokens)).strip()

View File

@@ -2,9 +2,10 @@
"""
class Node(object):
def __init__(self, p):
self._p = p
self.tokens = p
def parse(self, scope):
pass
# print(type(self), list(self._p))
# print()
return self
def format(self, fills):
return str(type(self))

View File

@@ -1,8 +1,31 @@
"""
"""
from .node import Node
from lesscpy.lessc import utility
class Property(Node):
pass
def parse(self, scope):
pass
# print(type(self), list(self._p))
# print()
property, style = self.tokens
self.property = property[0]
self.parsed = []
if style:
self.parsed = [p.parse(scope)
if hasattr(p, 'parse')
else p
for p in utility.flatten(style)]
self.parsed = [p for p in self.parsed if p]
return self
def format(self, fills):
"""
"""
f = "%(tab)s%(property)s:%(ws)s%(style)s;%(nl)s"
fills.update({
'property': self.property,
'style': ''.join([p.format(fills)
if hasattr(p, 'format')
else p
for p in self.parsed]),
})
return f % fills

View File

@@ -2,4 +2,7 @@
"""
from .node import Node
class Variable(Node):
pass
def parse(self, scope):
"""
"""
self.name = self.tokens.pop(0)

View File

@@ -0,0 +1,66 @@
import unittest
if __name__ == '__main__':
import bootstrap
from lesscpy.plib.expression import Expression
class TestExpression(unittest.TestCase):
def test_basic(self):
for test in [
['0', '+', '0', 0],
['2', '+', '2', 4],
['2.0', '+', '2', 4.0],
['2', '+', '2.0', 4.0],
['2.0', '+', '2.0', 4.0],
[('2.0',), '+', '2.0', 4.0],
[('2.0',), '+', ('2.0',), 4.0],
['0px', '+', '0', 0],
['2px', '+', '2', '4px'],
['2.0px', '+', '2', '4.0px'],
[('2px', ' '), '+', '2.0', '4.0px'],
['2.0px', '+', '2.0', '4.0px'],
]:
e = Expression(test[:3])
self.assertEqual(test[3], e.parse(None), str(test))
def test_neg(self):
for test in [
['-0', '+', '0', 0],
['-2', '+', '-2', -4],
['-2.0', '+', '-2', -4.0],
['-2', '+', '-2.0', -4.0],
['-2.0', '+', '-2.0', -4.0],
['-0', '-', '0', 0],
['-2', '-', '-2', 0],
['-2.0', '-', '2', -4.0],
['-2', '-', '-2.0', 0],
['2.0', '-', '-2.0', 4.0],
['-0px', '+', '0', 0],
['-2px', '+', '-2', '-4px'],
['-2.0', '+', '-2px', '-4.0px'],
['-2em', '+', '-2.0', '-4.0em'],
['-2.0s', '+', '-2.0s', '-4.0s'],
]:
e = Expression(test[:3])
self.assertEqual(test[3], e.parse(None), str(test))
def testnest(self):
e = Expression(['2', '-', '-2'])
f = Expression([['-', e], '*', '-3'])
self.assertEqual(f.parse(None), 12)
g = Expression(['2', '*', ['-', f]])
self.assertEqual(g.parse(None), -24)
h = Expression(['34', '-', ['-', g]])
self.assertEqual(h.parse(None), 10)
i = Expression([f, '-', h])
self.assertEqual(i.parse(None), 2)
def testdiv(self):
e = Expression(['1', '/', '3'])
def testinput(self):
e = Expression(['1a', '+', '1b'])
self.assertRaises(SyntaxError, e.parse, None)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,95 @@
import unittest
if __name__ == '__main__':
import bootstrap
import lesscpy.lessc.utility as utility
class TestUtility(unittest.TestCase):
def testanalyze(self):
test = utility.analyze_number
self.assertEqual((0, ''), test('0'))
self.assertEqual((1, ''), test('1'))
self.assertEqual(type(test('1')[0]), int)
self.assertEqual(type(test('-1')[0]), int)
self.assertEqual((1.0, ''), test('1.0'))
self.assertEqual(type(test('-1.0')[0]), float)
self.assertEqual((0, 'px'), test('0px'))
self.assertEqual((1, 'px'), test('1px'))
self.assertEqual((1.0, 'px'), test('1.0px'))
self.assertEqual((0, 'px'), test('-0px'))
self.assertEqual((-1, 'px'), test('-1px'))
self.assertEqual(type(test('-1px')[0]), int)
self.assertEqual((-1.0, 'px'), test('-1.0px'))
self.assertEqual(type(test('-1.0px')[0]), float)
self.assertRaises(SyntaxError, test, 'gg')
self.assertRaises(SyntaxError, test, '-o')
self.assertRaises(SyntaxError, test, '')
def testsplit_unit(self):
test = utility.split_unit
self.assertEqual(('', ''), test(None))
self.assertEqual(('', ''), test(False))
self.assertEqual(('', ''), test('qwerty'))
self.assertEqual(('1', ''), test(1))
self.assertEqual(('1', ''), test('1'))
self.assertEqual(('1', 'px'), test('1px'))
self.assertEqual(('-1', 'px'), test('-1px'))
def testis_int(self):
test = utility.is_int
self.assertTrue(test(1))
self.assertTrue(test('1'))
self.assertTrue(test('-1'))
self.assertTrue(test(-1))
self.assertFalse(test(False))
self.assertFalse(test(None))
self.assertFalse(test(0.0))
def testis_float(self):
test = utility.is_float
self.assertFalse(test(1))
self.assertFalse(test('1'))
self.assertFalse(test(False))
self.assertFalse(test(None))
self.assertTrue(test(0.0))
self.assertTrue(test(-0.0))
self.assertTrue(test('77.0565'))
self.assertTrue(test('-0.0'))
def testis_color(self):
test = utility.is_color
self.assertTrue(test('#123'))
self.assertTrue(test('#123456'))
self.assertTrue(test('#Df3'))
self.assertTrue(test('#AbCdEf'))
self.assertFalse(test('#AbCdEg'))
self.assertFalse(test('#h12345'))
self.assertFalse(test('#12345'))
self.assertFalse(test('AbCdEf'))
self.assertFalse(test(''))
self.assertFalse(test(False))
self.assertFalse(test([]))
def testis_variable(self):
test = utility.is_variable
self.assertTrue(test('@var'))
self.assertTrue(test('-@var'))
self.assertFalse(test('var'))
self.assertFalse(test(''))
self.assertFalse(test(False))
self.assertFalse(test([]))
def testwith_unit(self):
test = utility.with_unit
self.assertEqual('1px', test((1, 'px')))
self.assertEqual('1px', test(1, 'px'))
self.assertEqual('1.0px', test(1.0, 'px'))
self.assertEqual('0.0px', test('.0', 'px'))
self.assertEqual('0.6px', test(.6, 'px'))
self.assertEqual(1, test(1))
self.assertEqual(1, test(1, None))
self.assertEqual(1, test(1,))
if __name__ == '__main__':
unittest.main()