Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
csscom
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
This is an archived project. Repository and other project resources are read-only.
Show more breadcrumbs
Taddeüs Kroes
csscom
Commits
cc85a34e
Commit
cc85a34e
authored
12 years ago
by
Taddeüs Kroes
Browse files
Options
Downloads
Patches
Plain Diff
Added unit tests for parser and fixed a lot of bugs on the way
parent
596e89fd
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
parse.py
+54
-25
54 additions, 25 deletions
parse.py
tests/test_parse.py
+214
-1
214 additions, 1 deletion
tests/test_parse.py
with
268 additions
and
26 deletions
parse.py
+
54
−
25
View file @
cc85a34e
...
...
@@ -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
This diff is collapsed.
Click to expand it.
tests/test_parse.py
+
214
−
1
View file @
cc85a34e
...
...
@@ -3,10 +3,223 @@ from unittest import TestCase
from
parse
import
split_selectors
,
parse_groups
class
Test
Pa
rs
e
(
TestCase
):
class
Test
Helpe
rs
(
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
(
'
\n
a ,b
'
),
[
'
a
'
,
'
b
'
])
self
.
assertEqual
(
split_selectors
(
'
a, b
\n
c
'
),
[
'
a
'
,
'
b c
'
])
self
.
assertEqual
(
split_selectors
(
'
a,
\n
b 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\t
div {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
,
'
\n
div
\n
'
)
self
.
assertRaisesRegexp
(
Exception
,
'
unexpected <EOF> on line 2
'
,
parse_groups
,
'
\n
div {
'
)
def
test_error_property_name
(
self
):
self
.
assertRaisesRegexp
(
Exception
,
'
unexpected
\'
:
\'
on line 2
'
,
parse_groups
,
'
div{margin
\n
left: 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
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment