Refactor work
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 ':'
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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 ('','')
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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])
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
"""
|
||||
from .node import Node
|
||||
class Variable(Node):
|
||||
pass
|
||||
def parse(self, scope):
|
||||
"""
|
||||
"""
|
||||
self.name = self.tokens.pop(0)
|
||||
66
lesscpy/test/testexpression.py
Normal file
66
lesscpy/test/testexpression.py
Normal 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()
|
||||
95
lesscpy/test/testutility.py
Normal file
95
lesscpy/test/testutility.py
Normal 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()
|
||||
Reference in New Issue
Block a user