Commit cc85a34e authored by Taddeüs Kroes's avatar Taddeüs Kroes

Added unit tests for parser and fixed a lot of bugs on the way

parent 596e89fd
......@@ -3,7 +3,7 @@ def split_selectors(raw_selector):
Split a selector with commas and arbitrary whitespaces into a list of
selector swith single-space whitespaces.
"""
return [' '.join(s.split()) for s in raw_selector.split(',')]
return filter(None, (' '.join(s.split()) for s in raw_selector.split(',')))
def parse_groups(css):
......@@ -16,20 +16,12 @@ def parse_groups(css):
prev_char = None
lineno = 1
properties = []
current_group = root_group = []
groups = [(None, root_group)]
current_group = (None, [])
groups = []
selectors = None
comment = False
def parse_property():
assert selectors is not None
if stack.strip():
parts = stack.split(':', 1)
assert len(parts) == 2
name, value = map(str.strip, parts)
assert '\n' not in name
properties.append((name, value))
nesting_level = 0
property_name = None
try:
for c in css:
......@@ -43,32 +35,59 @@ def parse_groups(css):
if char == '/' and prev_char == '*':
comment = False
elif char == '{':
nesting_level += 1
# Block start
if selectors is not None:
# Block is nested, save group selector
current_group = []
groups.append((selectors, current_group))
# Block is nested, push current root group and continue
# with this group
if len(current_group[1]):
groups.append(current_group)
current_group = (selectors, [])
selectors = split_selectors(stack)
#print stack.strip(), '->', selectors
stack = ''
assert len(selectors)
elif char == '}':
# Last property may not have been closed with a semicolon
parse_property()
assert nesting_level > 0
nesting_level -= 1
if selectors is None:
# Closing group
current_group = root_group
groups.append(current_group)
current_group = (None, [])
else:
# Closing block
current_group.append((selectors, properties))
# Last property may not have been closed with a semicolon
property_value = stack.strip()
if len(property_value):
assert property_name is not None
properties.append((property_name, property_value))
property_name = None
stack = ''
current_group[1].append((selectors, properties))
selectors = None
properties = []
elif char == ';':
# Property definition
parse_property()
elif char == ':' and nesting_level > 0:
assert selectors is not None
# Property name
property_name = stack.strip()
assert '\n' not in property_name
stack = ''
elif char == ';':
# Property value
property_value = stack.strip()
if len(property_value):
assert property_name is not None
properties.append((property_name, property_value))
property_name = None
stack = ''
else:
assert property_name is None
elif char == '*' and prev_char == '/':
# Comment start
comment = True
......@@ -77,7 +96,17 @@ def parse_groups(css):
stack += char
prev_char = char
if len(current_group[1]):
groups.append(current_group)
if stack.split() or nesting_level > 0:
char = '<EOF>'
raise AssertionError()
except AssertionError:
raise Exception('unexpected \'%c\' on line %d' % (char, lineno))
if len(char) < 2:
char = "'" + char + "'"
raise Exception('unexpected %s on line %d' % (char, lineno))
return groups
......@@ -3,10 +3,223 @@ from unittest import TestCase
from parse import split_selectors, parse_groups
class TestParse(TestCase):
class TestHelpers(TestCase):
def test_split_selectors(self):
self.assertEqual(split_selectors(''), [])
self.assertEqual(split_selectors('a, b'), ['a', 'b'])
self.assertEqual(split_selectors('a ,b'), ['a', 'b'])
self.assertEqual(split_selectors('\na ,b '), ['a', 'b'])
self.assertEqual(split_selectors('a, b\nc'), ['a', 'b c'])
self.assertEqual(split_selectors('a,\nb c ,d\n'), ['a', 'b c', 'd'])
class TestParseGroups(TestCase):
def test_empty_stylesheet(self):
self.assertParse('', [])
self.assertParse(' \n ', [])
def test_empty_block(self):
self.assertParse('div {}', [(None, [(['div'], [])])])
def test_single_property(self):
self.assertParse('div {color:black;}',
[(None, [(['div'], [('color', 'black')])])])
self.assertParse('div {color:black}',
[(None, [(['div'], [('color', 'black')])])])
self.assertParse('''
div {
color: black;
}
''', [(None, [(['div'], [('color', 'black')])])])
self.assertParse('''
div {
color: black
}
''', [(None, [(['div'], [('color', 'black')])])])
def test_multiple_properties(self):
self.assertParse('''
div {
color: black;
border: none;
}
''', [(None, [(['div'], [('color', 'black'), ('border', 'none')])])])
self.assertParse('''
div {
color: black;
border: none
}
''', [(None, [(['div'], [('color', 'black'), ('border', 'none')])])])
def test_multiple_selectors(self):
self.assertParse('div,p {color: black}',
[(None, [(['div', 'p'], [('color', 'black')])])])
self.assertParse('div, p {color: black}',
[(None, [(['div', 'p'], [('color', 'black')])])])
self.assertParse('p,\n\tdiv {color: black}',
[(None, [(['p', 'div'], [('color', 'black')])])])
def test_single_group(self):
self.assertParse('''
@media (min-width: 970px) {
div {color: black}
}
''', [(['@media (min-width: 970px)'],
[(['div'], [('color', 'black')])])])
def test_single_group_multiple_blocks(self):
self.assertParse('''
@media (min-width: 970px) {
div {color: black}
p {margin: 0}
}
''', [(['@media (min-width: 970px)'],
[(['div'], [('color', 'black')]), (['p'], [('margin', '0')])])])
def test_multiple_groups(self):
self.assertParse('''
@media (min-width: 970px) {
div {color: black}
}
@media (max-width: 969px) {
div {color: red}
}
''', [
(['@media (min-width: 970px)'], [(['div'], [('color', 'black')])]),
(['@media (max-width: 969px)'], [(['div'], [('color', 'red')])])
])
def test_group_with_root(self):
self.assertParse('''
div {
color: black
}
@media (max-width: 969px) {
div {color: red}
}
''', [
(None, [(['div'], [('color', 'black')])]),
(['@media (max-width: 969px)'], [(['div'], [('color', 'red')])])
])
self.assertParse('''
@media (max-width: 969px) {
div {color: red}
}
div {
color: black
}
''', [
(['@media (max-width: 969px)'], [(['div'], [('color', 'red')])]),
(None, [(['div'], [('color', 'black')])])
])
def test_group_with_multiple_roots(self):
self.assertParse('''
div {
color: black
}
@media (max-width: 969px) {
div {color: red}
}
p {
margin: 0
}
''', [
(None, [(['div'], [('color', 'black')])]),
(['@media (max-width: 969px)'], [(['div'], [('color', 'red')])]),
(None, [(['p'], [('margin', '0')])])
])
def test_multiple_groups_multiple_roots(self):
self.assertParse('''
@media (min-width: 970px) {
div {color: black}
}
div {
color: black
}
@media (max-width: 969px) {
div {color: red}
}
p {
margin: 0
}
''', [
(['@media (min-width: 970px)'], [(['div'], [('color', 'black')])]),
(None, [(['div'], [('color', 'black')])]),
(['@media (max-width: 969px)'], [(['div'], [('color', 'red')])]),
(None, [(['p'], [('margin', '0')])])
])
self.assertParse('''
p {
margin: 0
}
@media (min-width: 970px) {
div {color: black}
}
div {
color: black
}
@media (max-width: 969px) {
div {color: red}
}
''', [
(None, [(['p'], [('margin', '0')])]),
(['@media (min-width: 970px)'], [(['div'], [('color', 'black')])]),
(None, [(['div'], [('color', 'black')])]),
(['@media (max-width: 969px)'], [(['div'], [('color', 'red')])]),
])
def test_comment(self):
self.assertParse('''
/* this is a comment */
div {color: black}
''', [(None, [(['div'], [('color', 'black')])])])
self.assertParse('''
/*
* this is a multiline comment
*/
div {color: black}
''', [(None, [(['div'], [('color', 'black')])])])
self.assertParse('''
div {color: /*inline comment*/ black}
''', [(None, [(['div'], [('color', 'black')])])])
def test_error_brackets(self):
self.assertRaisesRegexp(Exception, 'unexpected \'{\' on line 1',
parse_groups, '{')
self.assertRaisesRegexp(Exception, 'unexpected \'{\' on line 2',
parse_groups, 'div {\n{}')
self.assertRaisesRegexp(Exception, 'unexpected \'}\' on line 1',
parse_groups, '}')
self.assertRaisesRegexp(Exception, 'unexpected \'}\' on line 2',
parse_groups, 'div\n}')
def test_error_EOF(self):
self.assertRaisesRegexp(Exception, 'unexpected <EOF> on line 3',
parse_groups, '\ndiv\n')
self.assertRaisesRegexp(Exception, 'unexpected <EOF> on line 2',
parse_groups, '\ndiv {')
def test_error_property_name(self):
self.assertRaisesRegexp(Exception, 'unexpected \':\' on line 2',
parse_groups, 'div{margin\nleft: 0}')
def test_error_property_value(self):
self.assertRaisesRegexp(Exception, 'unexpected \';\' on line 1',
parse_groups, 'div{margin:;}')
self.assertRaisesRegexp(Exception, 'unexpected \';\' on line 1',
parse_groups, 'div{foo;}')
self.assertParse('div {;}', [(None, [(['div'], [])])])
def assertParse(self, css, result):
self.assertEqual(parse_groups(css), result)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment