mirror of
https://github.com/quantum5/win2xcur.git
synced 2025-04-24 10:11:57 -04:00
More mypy annotations
This commit is contained in:
parent
764d0f6ca4
commit
0ecc367178
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ 3.5, 3.6, 3.7, 3.8 ]
|
||||
python-version: [ 3.6, 3.7, 3.8, 3.9.0-alpha - 3.9 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
|
|
|
@ -37,7 +37,7 @@ def main() -> None:
|
|||
|
||||
check_xcursorgen()
|
||||
|
||||
def process(file):
|
||||
def process(file) -> None:
|
||||
name = file.name
|
||||
blob = file.read()
|
||||
try:
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
from typing import List, Type
|
||||
|
||||
from win2xcur.parser.ani import ANIParser
|
||||
from win2xcur.parser.base import BaseParser
|
||||
from win2xcur.parser.cur import CURParser
|
||||
|
||||
PARSERS = [CURParser, ANIParser]
|
||||
PARSERS: List[Type[BaseParser]] = [CURParser, ANIParser]
|
||||
|
||||
|
||||
def open_blob(blob):
|
||||
def open_blob(blob: bytes) -> BaseParser:
|
||||
for parser in PARSERS:
|
||||
if parser.can_parse(blob):
|
||||
return parser(blob)
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import struct
|
||||
from copy import copy
|
||||
from typing import Any, Iterable, List, Tuple
|
||||
|
||||
from win2xcur.cursor import CursorFrame
|
||||
from win2xcur.parser.base import BaseParser
|
||||
from win2xcur.parser.cur import CURParser
|
||||
|
||||
|
||||
class ANIParser:
|
||||
class ANIParser(BaseParser):
|
||||
SIGNATURE = b'RIFF'
|
||||
ANI_TYPE = b'ACON'
|
||||
FRAME_TYPE = b'fram'
|
||||
|
@ -16,26 +19,26 @@ class ANIParser:
|
|||
ICON_FLAG = 0x1
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, blob):
|
||||
def can_parse(cls, blob: bytes) -> bool:
|
||||
signature, size, subtype = cls.RIFF_HEADER.unpack(blob[:cls.RIFF_HEADER.size])
|
||||
return signature == cls.SIGNATURE and size == len(blob) - 8 and subtype == cls.ANI_TYPE
|
||||
|
||||
def __init__(self, blob):
|
||||
self.blob = blob
|
||||
def __init__(self, blob: bytes) -> None:
|
||||
super().__init__(blob)
|
||||
if not self.can_parse(blob):
|
||||
raise ValueError('Not a .ani file')
|
||||
self.frames = self._parse(self.RIFF_HEADER.size)
|
||||
|
||||
def _unpack(self, struct_cls, offset):
|
||||
def _unpack(self, struct_cls: struct.Struct, offset: int) -> Tuple[Any, ...]:
|
||||
return struct_cls.unpack(self.blob[offset:offset + struct_cls.size])
|
||||
|
||||
def _read_chunk(self, offset, expected):
|
||||
def _read_chunk(self, offset: int, expected: Iterable[bytes]) -> Tuple[int, int]:
|
||||
name, size = self._unpack(self.CHUNK_HEADER, offset)
|
||||
if name not in expected:
|
||||
raise ValueError('Expected chunk %r, found %r' % (expected, name))
|
||||
return size, offset + self.CHUNK_HEADER.size
|
||||
|
||||
def _parse(self, offset):
|
||||
def _parse(self, offset: int) -> List[CursorFrame]:
|
||||
size, offset = self._read_chunk(offset, expected=[b'anih'])
|
||||
|
||||
if size != self.ANIH_HEADER.size:
|
||||
|
|
18
win2xcur/parser/base.py
Normal file
18
win2xcur/parser/base.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from abc import ABCMeta, abstractmethod
|
||||
from typing import List
|
||||
|
||||
from win2xcur.cursor import CursorFrame
|
||||
|
||||
|
||||
class BaseParser(metaclass=ABCMeta):
|
||||
blob: bytes
|
||||
frames: List[CursorFrame]
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, blob: bytes) -> None:
|
||||
self.blob = blob
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def can_parse(cls, blob: bytes) -> bool:
|
||||
raise NotImplementedError()
|
|
@ -1,28 +1,30 @@
|
|||
import struct
|
||||
from typing import List, Tuple
|
||||
|
||||
from wand.image import Image
|
||||
|
||||
from win2xcur.cursor import CursorFrame, CursorImage
|
||||
from win2xcur.parser.base import BaseParser
|
||||
|
||||
|
||||
class CURParser:
|
||||
class CURParser(BaseParser):
|
||||
MAGIC = b'\0\0\02\0'
|
||||
ICON_DIR = struct.Struct('<HHH')
|
||||
ICON_DIR_ENTRY = struct.Struct('<BBBBHHII')
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, blob):
|
||||
def can_parse(cls, blob: bytes) -> bool:
|
||||
return blob[:len(cls.MAGIC)] == cls.MAGIC
|
||||
|
||||
def __init__(self, blob):
|
||||
self.blob = blob
|
||||
def __init__(self, blob: bytes) -> None:
|
||||
super().__init__(blob)
|
||||
self._image = Image(blob=blob, format='cur')
|
||||
self._hotspots = self._parse_header()
|
||||
self.frames = [CursorFrame([
|
||||
CursorImage(image, hotspot) for image, hotspot in zip(self._image.sequence, self._hotspots)
|
||||
])]
|
||||
|
||||
def _parse_header(self):
|
||||
def _parse_header(self) -> List[Tuple[int, int]]:
|
||||
reserved, ico_type, image_count = self.ICON_DIR.unpack(self.blob[:self.ICON_DIR.size])
|
||||
assert reserved == 0
|
||||
assert ico_type == 2
|
||||
|
|
|
@ -22,7 +22,7 @@ def apply_to_image(image: BaseImage, *, color: str, radius: float, sigma: float,
|
|||
return result
|
||||
|
||||
|
||||
def apply_to_frames(frames: List[CursorFrame], **kwargs):
|
||||
def apply_to_frames(frames: List[CursorFrame], **kwargs) -> None:
|
||||
for frame in frames:
|
||||
for cursor in frame:
|
||||
cursor.image = apply_to_image(cursor.image, **kwargs)
|
||||
|
|
Loading…
Reference in a new issue