-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Alignment class, refactor milestone to use it.
- Loading branch information
1 parent
a6b01ff
commit b924733
Showing
4 changed files
with
263 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
from dataclasses import asdict, astuple, dataclass | ||
from enum import Enum, EnumMeta | ||
from typing import Optional, Tuple, Union | ||
|
||
StrOrAlignment = Union[str, "Alignment"] | ||
|
||
|
||
class CaseInsensitiveEnumMeta(EnumMeta): | ||
def __getitem__(self, key): | ||
if isinstance(key, str): | ||
key = key.upper() | ||
return super().__getitem__(key) | ||
|
||
|
||
class AlignmentDirection(Enum, metaclass=CaseInsensitiveEnumMeta): | ||
CENTER = 1 | ||
CENTRE = 1 | ||
LEFT = 2 | ||
RIGHT = 3 | ||
|
||
|
||
class OffsetType(Enum, metaclass=CaseInsensitiveEnumMeta): | ||
UNIT = 1 | ||
PERCENT = 2 | ||
|
||
|
||
@dataclass(kw_only=True) | ||
class Alignment: | ||
direction: AlignmentDirection = (AlignmentDirection.CENTER,) | ||
offset_type: Optional[OffsetType] = None | ||
offset: Optional[Union[int, float]] = None | ||
|
||
@classmethod | ||
def from_value( | ||
cls, | ||
alignment: Optional[StrOrAlignment], | ||
default_offset_type: Optional[OffsetType] = None, | ||
default_offset: Optional[float] = None, | ||
) -> "Alignment": | ||
if alignment is None: | ||
return cls(offset_type=default_offset_type, offset=default_offset) | ||
if isinstance(alignment, Alignment): | ||
return cls.from_alignment(alignment) | ||
if isinstance(alignment, str): | ||
return cls.from_string(alignment) | ||
else: | ||
raise ValueError( | ||
'Invalid argument "alignment": expected None, str, or Alignment instance,' | ||
f" got {type(alignment).__name__}." | ||
) | ||
|
||
@classmethod | ||
def from_alignment(cls, alignment: "Alignment") -> "Alignment": | ||
kwargs = asdict(alignment) | ||
new = cls(**kwargs) | ||
return new | ||
|
||
@classmethod | ||
def from_string(cls, alignment: str) -> "Alignment": | ||
new = cls() | ||
new.update_from_alignment_string(alignment) | ||
return new | ||
|
||
@staticmethod | ||
def parse_offset(offset: str) -> Tuple[Union[int, float], OffsetType]: | ||
if offset.endswith("%"): | ||
return (float(offset[:-1]) / 100, OffsetType.PERCENT) | ||
else: | ||
return (int(offset), OffsetType.UNIT) | ||
|
||
def update_from_alignment_string(self, alignment: str) -> None: | ||
parts = alignment.split(":") | ||
|
||
try: | ||
self.direction = AlignmentDirection[parts[0]] | ||
except KeyError as e: | ||
raise ValueError( | ||
f'Invalid alignment direction "{parts[0]}".' | ||
f" Valid alignment directions are {[d.name for d in AlignmentDirection]}" | ||
) from e | ||
|
||
if len(parts) == 2: | ||
self.offset, self.offset_type = self.parse_offset(parts[1]) | ||
|
||
def as_tuple( | ||
self, | ||
) -> Tuple[AlignmentDirection, Optional[OffsetType], Optional[Union[int, float]]]: | ||
return astuple(self) | ||
|
||
def percent_of(self, whole: Union[int, float]) -> float: | ||
if self.offset_type != OffsetType.PERCENT: | ||
raise ValueError("Cannot return percent_of when offset_type != 'PERCENT'") | ||
return whole * self.offset | ||
|
||
def __str__(self): | ||
offset_str = "" | ||
if self.offset is not None: | ||
offset_str = ":" | ||
offset_str += ( | ||
f"{self.offset * 100}%" | ||
if self.offset_type == OffsetType.PERCENT | ||
else str(self.offset) | ||
) | ||
return f"{self.direction.name.lower()}{offset_str}" | ||
|
||
|
||
if __name__ == "__main__": | ||
result1 = Alignment.from_value("left:50%") | ||
print(result1) | ||
|
||
result2 = Alignment.from_value(result1) | ||
print(result2.as_tuple()) | ||
|
||
result3 = Alignment.from_value("Center") | ||
print(result3) | ||
|
||
# expect ValueError about type of argument. | ||
Alignment.from_value(1) | ||
|
||
# Expect ValueError about alignment direction. | ||
Alignment.from_value("widdershins:50") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import pytest | ||
|
||
from src.roadmapper.alignment import Alignment, AlignmentDirection, OffsetType | ||
|
||
|
||
@pytest.mark.unit | ||
def test_case_insensitivity(): | ||
assert AlignmentDirection["center"] == AlignmentDirection.CENTER | ||
assert AlignmentDirection["LEFT"] == AlignmentDirection.LEFT | ||
assert OffsetType["unit"] == OffsetType.UNIT | ||
|
||
|
||
@pytest.mark.unit | ||
def test_enum_direction_synonyms(): | ||
assert AlignmentDirection.CENTRE == AlignmentDirection.CENTER | ||
|
||
|
||
@pytest.mark.unit | ||
def test_alignment_from_string(): | ||
alignment = Alignment.from_value("left:50%") | ||
assert alignment.direction == AlignmentDirection.LEFT | ||
assert alignment.offset_type == OffsetType.PERCENT | ||
assert alignment.offset == 0.5 | ||
|
||
|
||
@pytest.mark.unit | ||
def test_from_string(): | ||
alignment = Alignment.from_string("right:30%") | ||
assert alignment.direction == AlignmentDirection.RIGHT | ||
assert alignment.offset_type == OffsetType.PERCENT | ||
assert alignment.offset == 0.3 | ||
|
||
|
||
@pytest.mark.unit | ||
def test_from_alignment(): | ||
alignment = Alignment(direction=AlignmentDirection.LEFT) | ||
new_alignment = Alignment.from_alignment(alignment) | ||
assert new_alignment.direction == AlignmentDirection.LEFT | ||
|
||
|
||
@pytest.mark.unit | ||
def test_parse_offset(): | ||
offset, offset_type = Alignment.parse_offset("50%") | ||
assert offset == 0.5 | ||
assert offset_type == OffsetType.PERCENT | ||
|
||
offset, offset_type = Alignment.parse_offset("30") | ||
assert offset == 30 | ||
assert offset_type == OffsetType.UNIT | ||
|
||
|
||
@pytest.mark.unit | ||
def test_update_from_alignment_string(): | ||
alignment = Alignment() | ||
alignment.update_from_alignment_string("centre:20%") | ||
assert alignment.direction == AlignmentDirection.CENTRE | ||
assert alignment.offset_type == OffsetType.PERCENT | ||
assert alignment.offset == 0.2 | ||
|
||
|
||
@pytest.mark.unit | ||
def test_alignment_from_alignment_object(): | ||
original_alignment = Alignment( | ||
direction=AlignmentDirection.RIGHT, offset_type=OffsetType.UNIT, offset=10 | ||
) | ||
new_alignment = Alignment.from_value(original_alignment) | ||
assert new_alignment.direction == AlignmentDirection.RIGHT | ||
assert new_alignment.offset_type == OffsetType.UNIT | ||
assert new_alignment.offset == 10 | ||
|
||
|
||
@pytest.mark.unit | ||
def test_as_tuple(): | ||
alignment = Alignment( | ||
direction=AlignmentDirection.RIGHT, offset_type=OffsetType.UNIT, offset=15 | ||
) | ||
assert alignment.as_tuple() == (AlignmentDirection.RIGHT, OffsetType.UNIT, 15) | ||
|
||
|
||
@pytest.mark.unit | ||
def test_percent_of(): | ||
alignment = Alignment(offset_type=OffsetType.PERCENT, offset=0.5) | ||
assert alignment.percent_of(100) == 50 | ||
|
||
alignment = Alignment(offset_type=OffsetType.UNIT, offset=30) | ||
with pytest.raises(ValueError): | ||
alignment.percent_of(100) | ||
|
||
|
||
@pytest.mark.unit | ||
def test_invalid_direction(): | ||
with pytest.raises(ValueError): | ||
Alignment.from_value("widdershins:50") | ||
|
||
|
||
@pytest.mark.unit | ||
def test_invalid_type(): | ||
with pytest.raises(ValueError): | ||
Alignment.from_value(1) | ||
|
||
|
||
@pytest.mark.unit | ||
def test_str_method(): | ||
alignment = Alignment( | ||
direction=AlignmentDirection.LEFT, offset_type=OffsetType.PERCENT, offset=0.25 | ||
) | ||
assert str(alignment) == "left:25.0%" | ||
|
||
alignment = Alignment( | ||
direction=AlignmentDirection.RIGHT, offset_type=OffsetType.UNIT, offset=10 | ||
) | ||
assert str(alignment) == "right:10" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters