From f36f2988926ad9e4e985d32d6836b2724c607156 Mon Sep 17 00:00:00 2001 From: goanpeca Date: Sun, 26 Apr 2020 20:01:21 -0500 Subject: [PATCH] Enable Flex declarations --- colosseum/declaration.py | 36 +++++------ colosseum/parser.py | 125 +++++++++++++++++++++++++++++++++++++++ colosseum/wrappers.py | 13 +++- 3 files changed, 156 insertions(+), 18 deletions(-) diff --git a/colosseum/declaration.py b/colosseum/declaration.py index 40320cbb8..f804056ae 100644 --- a/colosseum/declaration.py +++ b/colosseum/declaration.py @@ -1,5 +1,4 @@ -from . import engine as css_engine -from . import parser +from . import engine as css_engine, parser from .constants import ( # noqa ALIGN_CONTENT_CHOICES, ALIGN_ITEMS_CHOICES, ALIGN_SELF_CHOICES, AUTO, BACKGROUND_COLOR_CHOICES, BORDER_COLLAPSE_CHOICES, BORDER_COLOR_CHOICES, @@ -15,8 +14,8 @@ NOWRAP, ORDER_CHOICES, ORPHANS_CHOICES, OUTLINE_COLOR_CHOICES, OUTLINE_STYLE_CHOICES, OUTLINE_WIDTH_CHOICES, OVERFLOW_CHOICES, PADDING_CHOICES, PAGE_BREAK_AFTER_CHOICES, PAGE_BREAK_BEFORE_CHOICES, - PAGE_BREAK_INSIDE_CHOICES, POSITION_CHOICES, QUOTES_CHOICES, ROW, - SEPARATE, SHOW, SIZE_CHOICES, STATIC, STRETCH, TABLE_LAYOUT_CHOICES, + PAGE_BREAK_INSIDE_CHOICES, POSITION_CHOICES, QUOTES_CHOICES, ROW, SEPARATE, + SHOW, SIZE_CHOICES, STATIC, STRETCH, TABLE_LAYOUT_CHOICES, TEXT_ALIGN_CHOICES, TEXT_DECORATION_CHOICES, TEXT_INDENT_CHOICES, TEXT_TRANSFORM_CHOICES, TOP, TRANSPARENT, UNICODE_BIDI_CHOICES, VISIBILITY_CHOICES, VISIBLE, WHITE_SPACE_CHOICES, WIDOWS_CHOICES, @@ -24,7 +23,10 @@ TextAlignInitialValue, default, CURSOR_CHOICES, ) from .exceptions import ValidationError -from .wrappers import Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline +from .wrappers import ( # noqa + Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Flex, FlexFlow, + Outline +) _CSS_PROPERTIES = set() @@ -424,36 +426,36 @@ def __init__(self, **style): # 5. Ordering and orientation ######################################## # 5.1 Flex flow direction - # flex_direction = validated_property('flex_direction', choices=FLEX_DIRECTION_CHOICES, initial=ROW) + flex_direction = validated_property('flex_direction', choices=FLEX_DIRECTION_CHOICES, initial=ROW) # 5.2 Flex line wrapping - # flex_wrap = validated_property('flex_wrap', choices=FLEX_WRAP_CHOICES, initial=NOWRAP) + flex_wrap = validated_property('flex_wrap', choices=FLEX_WRAP_CHOICES, initial=NOWRAP) # 5.3 Flex direction and wrap - # flex_flow = + flex_flow = validated_shorthand_property('flex_flow', parser=parser.flex_flow, wrapper=FlexFlow) # 5.4 Display order - # order = validated_property('order', choices=ORDER_CHOICES, initial=0) + order = validated_property('order', choices=ORDER_CHOICES, initial=0) # 7. Flexibility ##################################################### # 7.2 Components of flexibility - # flex_grow = validated_property('flex_grow', choices=FLEX_GROW_CHOICES, initial=0) - # flex_shrink = validated_property('flex_shrink', choices=FLEX_SHRINK_CHOICES, initial=1) - # flex_basis = validated_property('flex_basis', choices=FLEX_BASIS_CHOICES, initial=AUTO) + flex_grow = validated_property('flex_grow', choices=FLEX_GROW_CHOICES, initial=0) + flex_shrink = validated_property('flex_shrink', choices=FLEX_SHRINK_CHOICES, initial=1) + flex_basis = validated_property('flex_basis', choices=FLEX_BASIS_CHOICES, initial=AUTO) # 7.1 The 'flex' shorthand - # flex = + flex = validated_shorthand_property('flex', parser=parser.flex, wrapper=Flex) # 8. Alignment ####################################################### # 8.2 Axis alignment - # justify_content = validated_property('justify_content', choices=JUSTIFY_CONTENT_CHOICES, initial=FLEX_START) + justify_content = validated_property('justify_content', choices=JUSTIFY_CONTENT_CHOICES, initial=FLEX_START) # 8.3 Cros-axis alignment - # align_items = validated_property('align_items', choices=ALIGN_ITEMS_CHOICES, initial=STRETCH) - # align_self = validated_property('align_self', choices=ALIGN_SELF_CHOICES, initial=AUTO) + align_items = validated_property('align_items', choices=ALIGN_ITEMS_CHOICES, initial=STRETCH) + align_self = validated_property('align_self', choices=ALIGN_SELF_CHOICES, initial=AUTO) # 8.4 Packing flex lines - # align_content = validated_property('align_content', choices=ALIGN_CONTENT_CHOICES, initial=STRETCH) + align_content = validated_property('align_content', choices=ALIGN_CONTENT_CHOICES, initial=STRETCH) ###################################################################### # Grid properties diff --git a/colosseum/parser.py b/colosseum/parser.py index 85b7acb8a..8bf5b3417 100644 --- a/colosseum/parser.py +++ b/colosseum/parser.py @@ -445,3 +445,128 @@ def cursor(values): raise ValueError('Value {value} is not a valid cursor value'.format(value=value)) return Cursor(validated_values) + + +############################################################################## +# Flex Flow +############################################################################## +def _parse_flex_flow_property_part(value, flex_flow_dict): + """Parse flex_flow shorthand property part for known properties.""" + from .constants import FLEX_DIRECTION_CHOICES, FLEX_WRAP_CHOICES + + property_validators = { + 'flex_direction': FLEX_DIRECTION_CHOICES, + 'flex_wrap': FLEX_WRAP_CHOICES, + } + + for property_name, choices in property_validators.items(): + try: + value = choices.validate(value) + except (ValueError, ValidationError): + continue + + if property_name in flex_flow_dict: + raise ValueError('Invalid duplicated property!') + + flex_flow_dict[property_name] = value + return flex_flow_dict + + raise ValueError('Flex flow value "{value}" not valid!'.format(value=value)) + + +def flex_flow(value): + """ + Parse flex flow string into a dictionary of properties. + + The font CSS property is a shorthand for flex-wrap and flex-direction. + + Reference: + - https://www.w3.org/TR/css-flexbox-1/#flex-flow-property + """ + if value: + if isinstance(value, str): + values = [val.strip() for val in value.split()] + elif isinstance(value, Sequence): + values = value + else: + raise ValueError('Unknown flex flow %s ' % value) + else: + raise ValueError('Unknown flex flow %s ' % value) + + # We iteratively split by the first left hand space found and try to validate if that part + # is a valid or (which can come in any order) + + # We use this dictionary to store parsed values and check that values properties are not + # duplicated + flex_flow_dict = {} + for idx, part in enumerate(values): + if idx > 1: + # Flex flow can have a maximum of 2 parts + raise ValueError('Flex flow property shorthand contains too many parts!') + + flex_flow_dict = _parse_flex_flow_property_part(part, flex_flow_dict) + + return flex_flow_dict + + +############################################################################## +# Flex +############################################################################## +def _parse_flex_property_part(value, flex_dict): + """Parse flex shorthand property part for known properties.""" + from .constants import FLEX_GROW_CHOICES, FLEX_SHRINK_CHOICES, FLEX_BASIS_CHOICES + + property_validators = { + 'flex_grow': FLEX_GROW_CHOICES, + 'flex_shrink': FLEX_SHRINK_CHOICES, + 'flex_basis': FLEX_BASIS_CHOICES, + } + + for property_name, choices in property_validators.items(): + try: + value = choices.validate(value) + except (ValueError, ValidationError): + continue + + if property_name in flex_dict: + raise ValueError('Invalid duplicated property!') + + flex_dict[property_name] = value + return flex_dict + + raise ValueError('Flex value "{value}" not valid!'.format(value=value)) + + +def flex(value): + """ + Parse flex string into a dictionary of properties. + + The font CSS property is a shorthand for flex-grow, flex-shrink and flex-basis. + + Reference: + - https://www.w3.org/TR/css-flexbox-1/#flex-property + """ + if value: + if isinstance(value, str): + values = [val.strip() for val in value.split()] + elif isinstance(value, Sequence): + values = value + else: + raise ValueError('Unknown flex %s ' % value) + else: + raise ValueError('Unknown flex %s ' % value) + + # We iteratively split by the first left hand space found and try to validate if that part + # is a valid or or (which can come in any order) + + # We use this dictionary to store parsed values and check that values properties are not + # duplicated + flex_dict = {} + for idx, part in enumerate(values): + if idx > 2: + # Flex can have a maximum of 3 parts + raise ValueError('Flex property shorthand contains too many parts!') + + flex_dict = _parse_flex_property_part(part, flex_dict) + + return flex_dict diff --git a/colosseum/wrappers.py b/colosseum/wrappers.py index 05809ff1c..33f369b9b 100644 --- a/colosseum/wrappers.py +++ b/colosseum/wrappers.py @@ -114,7 +114,7 @@ def __repr__(self): def __str__(self): parts = [] - for key, value in self.to_dict().items(): + for __, value in self.to_dict().items(): parts.append(str(value)) return ' '.join(parts) @@ -235,3 +235,14 @@ def sort(self, cmp=None, key=None, reverse=False): class Cursor(ImmutableList): """Immutable list to store cursor property.""" + + +############################################################################## +# Flex +############################################################################## +class FlexFlow(Shorthand): + VALID_KEYS = ['flex_direction', 'flex_wrap'] + + +class Flex(Shorthand): + VALID_KEYS = ['flex_grow', 'flex_shrink', 'flex_basis']