From 62781cfaed09124708440548c2635dfc268a52c5 Mon Sep 17 00:00:00 2001 From: Ted Kaemming Date: Thu, 4 Aug 2016 11:28:56 -0700 Subject: [PATCH] Support shorthand property compression. --- tests.py | 73 ++++++++++++++++++++++++++++++++++++++++++++ toronado/__init__.py | 67 +++++++++++++++++++++++++++++++--------- 2 files changed, 125 insertions(+), 15 deletions(-) diff --git a/tests.py b/tests.py index 6aa9452..40081bc 100644 --- a/tests.py +++ b/tests.py @@ -8,6 +8,7 @@ from toronado import ( Properties, Rule, + compress_box_property, expand_shorthand_box_property, from_string, inline, @@ -24,6 +25,68 @@ class TestCase(Exam, unittest.TestCase): pass +def test_compress_box_property(): + compress = compress_box_property('margin', 'margin-{}') + + assert compress({ + 'margin-top': '1px', + 'margin-right': '1px', + 'margin-bottom': '1px', + 'margin-left': '1px', + }) == { + 'margin': '1px', + } + + assert compress({ + 'margin-top': '1px', + 'margin-right': '2px', + 'margin-bottom': '1px', + 'margin-left': '2px', + }) == { + 'margin': '1px 2px', + } + + assert compress({ + 'margin-top': '1px', + 'margin-right': '2px', + 'margin-bottom': '3px', + 'margin-left': '2px', + }) == { + 'margin': '1px 2px 3px', + } + + assert compress({ + 'margin-top': '1px', + 'margin-right': '2px', + 'margin-bottom': '3px', + 'margin-left': '4px', + }) == { + 'margin': '1px 2px 3px 4px', + } + + assert compress({ + 'margin-top': '1px', + 'margin-bottom': '1px', + }) == { + 'margin-top': '1px', + 'margin-bottom': '1px', + } + + assert compress({ + 'margin-top': '1px', + 'margin-right': '1px', + 'margin-bottom': '1px', + 'margin-left': '1px', + 'other-property': 'foo', + }) == { + 'margin': '1px', + 'other-property': 'foo', + } + + properties = {} + assert compress(properties) is properties + + def test_expand_shorthand_box_property(): expand = expand_shorthand_box_property('margin-{}') @@ -113,6 +176,16 @@ def test_serializes_to_attribute_string(self): self.assertIn('%s' % (properties,), expected) + def test_compresses_shorthand_properties(self): + properties = Properties({ + 'margin-top': '10px', + 'margin-right': '10px', + 'margin-bottom': '10px', + 'margin-left': '10px', + }) + + assert '%s' % (properties,) == 'margin: 10px' + def test_from_string(self): properties = Properties.from_string('color: red; font-weight: bold') self.assertEqual(properties, { diff --git a/toronado/__init__.py b/toronado/__init__.py index a6c87ba..a42d86a 100644 --- a/toronado/__init__.py +++ b/toronado/__init__.py @@ -27,8 +27,12 @@ logger = logging.getLogger(__name__) +def expand_box_property_names(template): + return list(map(template.format, ('top', 'right', 'bottom', 'left'))) + + def expand_shorthand_box_property(template): - sides = ('top', 'right', 'bottom', 'left') + names = expand_box_property_names(template) def expand_property(value): bits = value.split() @@ -44,18 +48,11 @@ def expand_property(value): else: raise ValueError('incorrect number of values for box rule: %s' % size) - return {template.format(side): value for side, value in zip(sides, result)} + return {name: value for name, value in zip(names, result)} return expand_property -rewrite_map = { - 'margin': expand_shorthand_box_property('margin-{}'), - 'padding': expand_shorthand_box_property('padding-{}'), - 'border-width': expand_shorthand_box_property('border-{}-width'), -} - - def warn_unsupported_shorthand_property(property): def expand_property(value): logger.warning( @@ -69,6 +66,35 @@ def expand_property(value): return expand_property +def compress_box_property(shorthand, template): + names = expand_box_property_names(template) + + def compress_property(value): + if not set(value).issuperset(set(names)): + return value + + top, right, bottom, left = map(value.pop, names) + + if top == right == bottom == left: + value[shorthand] = top + elif top == bottom and right == left: + value[shorthand] = '{} {}'.format(top, right) + elif right == left: + value[shorthand] = '{} {} {}'.format(top, right, bottom) + else: + value[shorthand] = '{} {} {} {}'.format(top, right, bottom, left) + + return value + + return compress_property + + +shorthand_box_properties = { + 'margin': 'margin-{}', + 'padding': 'padding-{}', + 'border-width': 'border-{}-width', +} + unsupported_shorthand_properties = ( 'animation', 'background', @@ -87,12 +113,19 @@ def expand_property(value): ) +expansion_rewrite_map = {} +property_processors = [] + +for property, template in shorthand_box_properties.items(): + expansion_rewrite_map[property] = expand_shorthand_box_property(template) + property_processors.append(compress_box_property(property, template)) + for property in unsupported_shorthand_properties: - rewrite_map[property] = warn_unsupported_shorthand_property(property) + expansion_rewrite_map[property] = warn_unsupported_shorthand_property(property) -def rewrite_property(property): - result = rewrite_map.get( +def expand_property(property): + result = expansion_rewrite_map.get( property.name, lambda value: { property.name: value, @@ -122,13 +155,17 @@ def __unicode__(self): Renders the properties as a string suitable for inclusion as a HTML tag attribute. """ - return '; '.join(map(': '.join, self.items())) + value = self.copy() + for processor in property_processors: + value = processor(value) + + return '; '.join(map(': '.join, value.items())) @classmethod def from_string(cls, value): values = {} for property in CSSStyleDeclaration(value).getProperties(): - values.update(rewrite_property(property)) + values.update(expand_property(property)) return cls(values) @@ -218,7 +255,7 @@ def inline(tree): for rule in ifilter(is_style_rule, stylesheet_parser.parseString(stylesheet.text)): properties = {} for property in rule.style: - properties.update(rewrite_property(property)) + properties.update(expand_property(property)) # XXX: This doesn't handle selectors with odd multiple whitespace. for selector in map(text_type.strip, rule.selectorText.split(',')):