Skip to content
Snippets Groups Projects
Commit 5adff8a5 authored by Taddeüs Kroes's avatar Taddeüs Kroes
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
*.pyc
*.swp
*~
coverage
.coverage
Makefile 0 → 100644
TEST_OPTIONS := --failfast --catch
COVERAGE_OPTIONS := --branch
COVERAGE_DIR := coverage
.PHONY: test coverage pyclean clean
test:
@PYTHONDONTWRITEBYTECODE=x python -m unittest discover -s tests \
-p 'test_*.py' $(TEST_OPTIONS)
coverage:
@python-coverage erase
@rm -rf $(COVERAGE_DIR)
@PYTHONDONTWRITEBYTECODE=x PYTHONPATH=. python-coverage run --source=. \
--omit=tests/* $(COVERAGE_OPTIONS) tests/run.py
@python-coverage report
@python-coverage html --directory=$(COVERAGE_DIR)
pyclean:
find -name \*.pyc -delete
clean: pyclean
rm -rf $(COVERAGE_DIR)
block.py 0 → 100644
import properties as props
class Block(object):
"""
A Block is a stylesheet block of the following form:
<selector>,
... {
<property>: <value>;
...
}
"""
def __init__(self, *args):
self.selectors = sorted(set(args))
self.properties = set()
def add_property(self, name, value):
self.properties.add((name, value))
def generate_css(self, compress_whitespace=False, compress_color=False,
compress_font=False, compress_dimension=False):
if compress_whitespace:
comma = ','
colon = ':'
newline = ''
lbracket = '{'
rbracket = '}'
else:
comma = ',\n'
colon = ': '
newline = '\n\t'
lbracket = ' {'
rbracket = '\n}'
properties = self.properties
if compress_color:
properties = props.compress_color(properties)
if compress_font:
properties = props.compress_font(properties)
if compress_dimension:
properties = props.compress_dimension(properties)
selector = comma.join(self.selectors)
properties = [newline + name + colon + value
for name, value in self.properties]
inner = ';'.join(properties)
if len(properties) and not compress_whitespace:
inner += ';'
return selector + lbracket + inner + rbracket
def __repr__(self): # pragma: nocover
return '<Block "%s" properties=%d>' \
% (', '.join(self.selectors), len(self.properties))
def __str__(self):
return self.generate_css()
#!/usr/bin/env python
from block import Block
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(',')]
class StyleSheet(object):
def __init__(self, css=None):
# List of encountered blocks
self.blocks = []
# Map of property stringification to list of containing blocks
self.property_locations = {}
if css:
self.parse_css(css)
def parse_css(self, css):
"""
Parse CSS code one character at a time. This is more efficient than
simply splitting on brackets, especially for large style sheets. All
comments are ignored (both inline and multiline).
"""
stack = char = ''
prev = block = None
lineno = 1
multiline_comment = inline_comment = False
try:
for c in css:
char = c
if multiline_comment:
# Multiline comment end?
if c == '/' and prev == '*':
multiline_comment = False
elif inline_comment:
# Inline comment end?
if c == '\n':
inline_comment = False
elif c == '{':
# Block start
selectors = split_selectors(stack)
stack = ''
assert len(selectors)
block = Block(*selectors)
elif c == '}':
# Block end
assert block is not None
block = None
elif c == ';':
# Property definition
assert block is not None
name, value = map(str.strip, stack.split(':', 1))
assert '\n' not in name
block.add_property(name, value)
elif c == '*' and prev == '/':
# Multiline comment start
multiline_comment = True
elif c == '/' and prev == '/':
# Inline comment start
inline_comment = True
else:
if c == '\n':
lineno += 1
stack += c
prev = c
except AssertionError:
raise Exception('unexpected \'%c\' on line %d' % (char, lineno))
def generate_css(self, compress_whitespace=False, compress_color=False,
compress_font=False, compress_dimension=False,
compress_blocks=False):
"""
Generate CSS code for the entire stylesheet.
Options:
compress_whitespace | Omit unnecessary whitespaces and semicolons.
compress_color | Replace color codes/names with shorter synonyms.
compress_font | Replace separate font statements with shortcut
| font statement where possible.
compress_dimension | Replace separate margin/padding statements with
| shortcut statements where possible.
compress_blocks | Combine or split blocks into blocks with
| comma-separated selectors if it results in less
| CSS code.
"""
blocks = self.blocks
if compress_blocks:
blocks = self._compress_blocks()
options = dict(compress_whitespace=compress_whitespace,
compress_color=compress_color,
compress_font=compress_font,
compress_dimension=compress_dimension)
newline = '' if compress_whitespace else '\n'
return newline.join(block.generate_css(**options)
for block in self.blocks)
def _compress_blocks(self):
pass
#for block in self.blocks
def compress(self, **kwargs):
"""
Shortcut for `generate_css`, with all compression options enabled by
default. Keyword argument names are preceded by 'compress_' before
being passed to `generate_css`.
"""
options = dict(whitespace=True, color=True, font=True, dimension=True,
blocks=True)
options.update(kwargs)
options = dict([('compress_' + k, v) for k, v in options.items()])
return self.generate_css(**options)
def __str__(self):
return self.generate_css()
if __name__ == '__main__': # pragma: nocover
# TODO: Command-line options parser
pass
def compress_color(properties):
return properties
def compress_font(properties):
return properties
def compress_dimension(properties):
return properties
import unittest
tests = unittest.defaultTestLoader.discover('tests', 'test_*.py')
run_options = dict(failfast=True)
if __name__ == '__main__':
unittest.TextTestRunner(**run_options).run(tests)
print
from unittest import TestCase
from csscomp import split_selectors, StyleSheet
class TestBase(TestCase):
def test_split_selectors(self):
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'])
from unittest import TestCase
from block import Block
class TestBlock(TestCase):
def test_constructor(self):
s = '#foo div'
b = Block(s)
self.assertEqual(b.selectors, [s])
self.assertEqual(b.properties, set())
class TestBlockAddProperty(TestCase):
def setUp(self):
self.b = Block('foo')
def test_single_value(self):
self.b.add_property('foo', 'bar')
self.assertEqual(self.b.properties, set([('foo', 'bar')]))
def test_double_value(self):
self.b.add_property('foo', 'bar')
self.b.add_property('foo', 'bar')
self.assertEqual(self.b.properties, set([('foo', 'bar')]))
def test_multiple_values(self):
self.b.add_property('foo', 'bar')
self.b.add_property('foo', 'baz')
self.assertEqual(self.b.properties, set([('foo', 'bar'),
('foo', 'baz')]))
class TestBlockGenerateCss(TestCase):
def setUp(self):
self.div = Block('div')
self.div.add_property('color', '#000')
self.div.add_property('font-weight', 'bold')
def test_nocompress_empty(self):
div = Block('div')
self.assertEqual(div.generate_css(), 'div {\n}')
def test_nocompress_single_property(self):
div = Block('div')
div.add_property('color', '#000')
self.assertEqualCss(div.generate_css(), '''
div {
color: #000;
}''')
def test_nocompress_multiple_properties(self):
self.assertEqualCss(self.div.generate_css(), '''
div {
color: #000;
font-weight: bold;
}''')
def test_nocompress_multiple_selectors(self):
div = Block('div', 'p')
div.add_property('color', '#000')
self.assertEqualCss(div.generate_css(), '''
div,
p {
color: #000;
}''')
def test_nocompress_multiple_selectors_sorted(self):
self.assertMultiLineEqual(Block('div', 'p').generate_css(),
Block('p', 'div').generate_css())
def test_compress_whitespace(self):
self.assertEqual(self.div.generate_css(compress_whitespace=True),
'div{color:#000;font-weight:bold}')
def test___str__(self):
self.assertEqual(str(self.div), self.div.generate_css())
def assertEqualCss(self, a, b):
self.assertMultiLineEqual(a, b.strip().replace(' ', '\t'))
from unittest import TestCase
from properties import compress_color, compress_font, compress_dimension
class TestProperties(TestCase):
def test_compress_color(self):
pass
def test_compress_font(self):
pass
def test_compress_dimension(self):
pass
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment