Skip to content

Commit

Permalink
Add parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
goanpeca committed Apr 27, 2020
1 parent 61880b7 commit d29016b
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 25 deletions.
18 changes: 17 additions & 1 deletion colosseum/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .validators import (ValidationError, is_border_spacing, is_color,
is_cursor, is_integer, is_length, is_number,
is_percentage, is_quote, is_rect)
is_percentage, is_position, is_quote, is_rect, is_uri)


class Choices:
Expand Down Expand Up @@ -350,9 +350,25 @@ def value(self, context):
BACKGROUND_COLOR_CHOICES = Choices(default, TRANSPARENT, validators=[is_color])

# background_image
BACKGROUND_IMAGE_CHOICES = Choices(validators=[is_uri], explicit_defaulting_constants=[INHERIT])

# background_repeat
REPEAT = 'repeat'
REPEAT_X = 'repeat-x'
REPEAT_Y = 'repeat-y'
NO_REPEAT = 'no-repeat'

BACKGROUND_REPEAT_CHOICES = Choices(REPEAT, REPEAT_X, REPEAT_Y, NO_REPEAT, explicit_defaulting_constants=[INHERIT])

# background_attachment
SCROLL = 'scroll'
FIXED = 'fixed'

BACKGROUND_ATTACHMENT_CHOICES = Choices(SCROLL, FIXED, explicit_defaulting_constants=[INHERIT])

# background_position
BACKGROUND_POSITION_CHOICES = Choices(validators=is_position, explicit_defaulting_constants=[INHERIT])

# background

######################################################################
Expand Down
45 changes: 25 additions & 20 deletions colosseum/declaration.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
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,
BACKGROUND_ATTACHMENT_CHOICES, BACKGROUND_COLOR_CHOICES,
BACKGROUND_IMAGE_CHOICES, BACKGROUND_POSITION_CHOICES,
BACKGROUND_REPEAT_CHOICES, BORDER_COLLAPSE_CHOICES, BORDER_COLOR_CHOICES,
BORDER_SPACING_CHOICES, BORDER_STYLE_CHOICES, BORDER_WIDTH_CHOICES,
BOX_OFFSET_CHOICES, CAPTION_SIDE_CHOICES, CLEAR_CHOICES, CLIP_CHOICES,
COLOR_CHOICES, DIRECTION_CHOICES, DISPLAY_CHOICES, EMPTY_CELLS_CHOICES,
FLEX_BASIS_CHOICES, FLEX_DIRECTION_CHOICES, FLEX_GROW_CHOICES,
FLEX_SHRINK_CHOICES, FLEX_START, FLEX_WRAP_CHOICES, FLOAT_CHOICES,
GRID_AUTO_CHOICES, GRID_AUTO_FLOW_CHOICES, GRID_GAP_CHOICES,
COLOR_CHOICES, CURSOR_CHOICES, DIRECTION_CHOICES, DISPLAY_CHOICES,
EMPTY_CELLS_CHOICES, FLEX_BASIS_CHOICES, FLEX_DIRECTION_CHOICES,
FLEX_GROW_CHOICES, FLEX_SHRINK_CHOICES, FLEX_START, FLEX_WRAP_CHOICES,
FLOAT_CHOICES, GRID_AUTO_CHOICES, GRID_AUTO_FLOW_CHOICES, GRID_GAP_CHOICES,
GRID_PLACEMENT_CHOICES, GRID_TEMPLATE_AREA_CHOICES, GRID_TEMPLATE_CHOICES,
INITIAL, INLINE, INVERT, JUSTIFY_CONTENT_CHOICES, LETTER_SPACING_CHOICES,
LTR, MARGIN_CHOICES, MAX_SIZE_CHOICES, MEDIUM, MIN_SIZE_CHOICES, NORMAL,
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,
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,
WORD_SPACING_CHOICES, Z_INDEX_CHOICES, OtherProperty,
TextAlignInitialValue, default, CURSOR_CHOICES,
PAGE_BREAK_INSIDE_CHOICES, POSITION_CHOICES, QUOTES_CHOICES, REPEAT, ROW,
SCROLL, 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, WORD_SPACING_CHOICES, Z_INDEX_CHOICES, OtherProperty,
TextAlignInitialValue, default,
)
from .exceptions import ValidationError
from .wrappers import Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline
from .wrappers import (
Border, BorderBottom, BorderLeft, BorderRight, BorderTop, Outline,
)

_CSS_PROPERTIES = set()

Expand Down Expand Up @@ -350,11 +353,13 @@ def __init__(self, **style):

# 14.2.1 Background properties
background_color = validated_property('background_color', choices=BACKGROUND_COLOR_CHOICES, initial=default)
# background_image
# background_repeat
# background_attachment
# background_position
# background
background_image = validated_property('background_image', choices=BACKGROUND_IMAGE_CHOICES, initial=None)
background_repeat = validated_property('background_repeat', choices=BACKGROUND_REPEAT_CHOICES, initial=REPEAT)
background_attachment = validated_property('background_attachment', choices=BACKGROUND_ATTACHMENT_CHOICES,
initial=SCROLL)
background_position = validated_property('background_position', choices=BACKGROUND_POSITION_CHOICES,
initial=('0%', '0%'))
background = validated_shorthand_property('background', parser=parser.background, wrapper=Background)

# 15. Fonts ##########################################################
# 15.3 Font family
Expand Down
130 changes: 128 additions & 2 deletions colosseum/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .exceptions import ValidationError
from .shapes import Rect
from .units import Unit, px
from .wrappers import BorderSpacing, Cursor, Quotes, Uri
from .wrappers import BorderSpacing, Cursor, Position, Quotes, Uri


def units(value):
Expand Down Expand Up @@ -286,7 +286,7 @@ def outline(value):


##############################################################################
# Border shorthands
# # Border shorthands
##############################################################################
def _parse_border_property_part(value, border_dict, direction=None):
"""Parse border shorthand property part for known properties."""
Expand Down Expand Up @@ -445,3 +445,129 @@ def cursor(values):
raise ValueError('Value {value} is not a valid cursor value'.format(value=value))

return Cursor(validated_values)


##############################################################################
# Background
##############################################################################
def position(value):
"""
[[ <percentage> | <length> | left | center | right ][ <percentage> | <length> | top | center | bottom ]? ] |
[[ left | center | right ] || [ top | center | bottom ]]
"""
if value:
if isinstance(value, str):
values = [val.strip() for val in value.split()]
elif isinstance(value, Sequence):
values = value
else:
raise ValueError('Unknown position %s ' % value)
else:
raise ValueError('Unknown position %s ' % value)

if len(values) == 1:
# If only one value is specified, the second value is assumed to be 'center'.
# If at least one value is not a keyword, then the first value represents the horizontal
# position and the second represents the vertical position. Negative <percentage> and
# <length> values are allowed.
try:
return Position(horizontal=units(values[0]))
except ValueError as error:
if values[0] in ['left', 'right', 'center']:
return Position(horizontal=values[0])

if values[0] in ['top', 'bottom']:
return Position(vertical=values[0])

elif len(values) == 2:
horizontal, vertical = None, None

# Check first value
try:
horizontal = units(values[0])
except ValueError as error:
if values[0] in ['left', 'center', 'right']:
horizontal = values[0]

if values[0] in ['top', 'center', 'bottom']:
vertical = values[0]

# Check second value
try:
vertical = units(values[1])
except ValueError as error:
if values[1] in ['left', 'center', 'right']:
horizontal = values[1]

if values[1] in ['top', 'center', 'bottom']:
vertical = values[1]

return Position(horizontal=horizontal, vertical=vertical)

raise ValueError('Position contains too many parts!')


##############################################################################
# Background shorthand
##############################################################################
def _parse_background_property_part(value, outline_dict):
"""Parse background shorthand property part for known properties."""
from .constants import ( # noqa
BACKGROUND_ATTACHMENT_CHOICES, BACKGROUND_COLOR_CHOICES, BACKGROUND_IMAGE_CHOICES,
BACKGROUND_POSITION_CHOICES, BACKGROUND_REPEAT_CHOICES
)

for property_name, choices in {'background_color': BACKGROUND_COLOR_CHOICES,
'background_image': BACKGROUND_IMAGE_CHOICES,
'background_repeat': BACKGROUND_REPEAT_CHOICES,
'background_attachment': BACKGROUND_ATTACHMENT_CHOICES,
'background_position': BACKGROUND_POSITION_CHOICES}.items():
try:
value = choices.validate(value)
except (ValueError, ValidationError):
continue

if property_name in border_dict:
raise ValueError('Invalid duplicated property!')

border_dict[property_name] = value
return border_dict

raise ValueError('Background value "{value}" not valid!'.format(value=value))


def background(value):
"""
Parse background string into a dictionary of background properties.
The font CSS property is a shorthand for background-color, background-image,
background-repeat, background-attachment, and background-position.
Reference:
- https://www.w3.org/TR/2011/REC-CSS2-20110607/colors.html#background-properties
"""
if value:
if isinstance(value, str):
values = [val.strip() for val in value.split()]
elif isinstance(value, Sequence):
values = value
else:
raise ValueError('Unknown background %s ' % value)
else:
raise ValueError('Unknown background %s ' % value)

# We iteratively split by the first left hand space found and try to validate if that part
# is a valid <background-color> or <background-image> or <background-repeat> or
# <background-attachment> or <background-position> (which can come in any order)

# We us this dictionary to store parsed values and check that values properties are not
# duplicated
background_dict = {}
for idx, part in enumerate(values):
if idx > 2:
# Outline can have a maximum of 3 parts
raise ValueError('Background property shorthand contains too many parts!')

background_dict = _parse_background_property_part(part, background_dict)

return background_dict
17 changes: 15 additions & 2 deletions colosseum/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,22 @@ def is_cursor(value):
except ValueError as error:
raise ValidationError(str(error))

return value


is_cursor.description = ('[ [<uri> ,]* [ auto | crosshair | default | pointer | move | e-resize '
'| ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize '
'| w-resize | text | wait | help | progress ] ]')


def is_position(value):
"""Check if given value is of content quotes and return it."""
try:
value = parser.position(value)
except ValueError:
raise ValidationError('Value {value} is not a valid position'.format(value=value))

return value


is_position.description = ('[ [ <percentage> | <length> | left | center | right ] '
'[ <percentage> | <length> | top | center | bottom ]? ] | '
'[ [ left | center | right ] || [ top | center | bottom ] ]')
45 changes: 45 additions & 0 deletions colosseum/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,51 @@ def vertical(self):
return self._horizontal if self._vertical is None else self._vertical


class Position:
"""
Position wrapper.
Examples:
Position(1px)
Position(1px, 2px)
Position('center', 2px)
Position('center', 'top')
"""

def __init__(self, horizontal=None, vertical=None):
self._horizontal = horizontal
self._vertical = vertical

def __repr__(self):
pass
# if self._vertical is None:
# string = 'Position({horizontal})'.format(horizontal=repr(self._horizontal))
# else:
# string = 'Position({horizontal}, {vertical})'.format(horizontal=repr(self._horizontal),
# vertical=repr(self._vertical))
# return string

def __str__(self):
pass
# if self._vertical is not None:
# string = '{horizontal} {vertical}'.format(horizontal=self._horizontal,
# vertical=self._vertical)
# else:
# string = '{horizontal}'.format(horizontal=self._horizontal)

# return string

@property
def horizontal(self):
"""Return the horizontal position."""
return 'center' if self._horizontal is None else self._horizontal

@property
def vertical(self):
"""Return the vertical position."""
return 'center' if self._vertical is None else self._vertical


class Quotes:
"""
Content opening and closing quotes wrapper.
Expand Down

0 comments on commit d29016b

Please sign in to comment.