2024-05-10 15:51:13 -07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import dataclasses
|
|
|
|
import sys
|
|
|
|
from collections import defaultdict
|
|
|
|
from collections.abc import Collection
|
|
|
|
from enum import Enum, auto
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
from comicapi.utils import norm_fold
|
|
|
|
|
|
|
|
if sys.version_info < (3, 11):
|
|
|
|
|
|
|
|
class StrEnum(str, Enum):
|
|
|
|
"""
|
|
|
|
Enum where members are also (and must be) strings
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __new__(cls, *values: Any) -> Any:
|
|
|
|
"values must already be of type `str`"
|
|
|
|
if len(values) > 3:
|
|
|
|
raise TypeError(f"too many arguments for str(): {values!r}")
|
|
|
|
if len(values) == 1:
|
|
|
|
# it must be a string
|
|
|
|
if not isinstance(values[0], str):
|
|
|
|
raise TypeError(f"{values[0]!r} is not a string")
|
|
|
|
if len(values) >= 2:
|
|
|
|
# check that encoding argument is a string
|
|
|
|
if not isinstance(values[1], str):
|
|
|
|
raise TypeError(f"encoding must be a string, not {values[1]!r}")
|
|
|
|
if len(values) == 3:
|
|
|
|
# check that errors argument is a string
|
|
|
|
if not isinstance(values[2], str):
|
|
|
|
raise TypeError("errors must be a string, not %r" % (values[2]))
|
|
|
|
value = str(*values)
|
|
|
|
member = str.__new__(cls, value)
|
|
|
|
member._value_ = value
|
|
|
|
return member
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _generate_next_value_(name: str, start: int, count: int, last_values: Any) -> str:
|
|
|
|
"""
|
|
|
|
Return the lower-cased version of the member name.
|
|
|
|
"""
|
|
|
|
return name.lower()
|
|
|
|
|
|
|
|
else:
|
|
|
|
from enum import StrEnum
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class Credit:
|
|
|
|
person: str = ""
|
|
|
|
role: str = ""
|
|
|
|
primary: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
class Mode(StrEnum):
|
|
|
|
OVERLAY = auto()
|
|
|
|
ADD_MISSING = auto()
|
|
|
|
|
|
|
|
|
|
|
|
def merge_lists(old: Collection[Any], new: Collection[Any]) -> list[Any] | set[Any]:
|
|
|
|
"""Dedupes normalised (NFKD), casefolded values using 'new' values on collisions"""
|
|
|
|
if len(new) == 0:
|
|
|
|
return old if isinstance(old, set) else list(old)
|
|
|
|
if len(old) == 0:
|
|
|
|
return new if isinstance(new, set) else list(new)
|
|
|
|
|
|
|
|
# Create dict to preserve case
|
|
|
|
new_dict = {norm_fold(str(n)): n for n in new}
|
|
|
|
old_dict = {norm_fold(str(c)): c for c in old}
|
|
|
|
|
|
|
|
old_dict.update(new_dict)
|
|
|
|
|
|
|
|
if isinstance(old, set):
|
|
|
|
return set(old_dict.values())
|
|
|
|
|
|
|
|
return list(old_dict.values())
|
|
|
|
|
|
|
|
|
|
|
|
def overlay(old: Any, new: Any) -> Any:
|
|
|
|
"""overlay - When the `new` object is not empty, replace `old` with `new`."""
|
2024-05-11 08:42:24 -07:00
|
|
|
if new is None or (isinstance(new, Collection) and len(new) == 0):
|
2024-05-10 15:51:13 -07:00
|
|
|
return old
|
|
|
|
|
|
|
|
return new
|
|
|
|
|
|
|
|
|
2024-05-11 08:42:24 -07:00
|
|
|
attribute = defaultdict(
|
|
|
|
lambda: overlay,
|
|
|
|
{
|
|
|
|
Mode.OVERLAY: overlay,
|
|
|
|
Mode.ADD_MISSING: lambda old, new: overlay(new, old),
|
|
|
|
},
|
|
|
|
)
|
2024-05-10 15:51:13 -07:00
|
|
|
|
|
|
|
|
2024-05-11 08:42:24 -07:00
|
|
|
lists = defaultdict(
|
2024-05-10 15:51:13 -07:00
|
|
|
lambda: overlay,
|
|
|
|
{
|
2024-05-11 08:42:24 -07:00
|
|
|
Mode.OVERLAY: merge_lists,
|
|
|
|
Mode.ADD_MISSING: lambda old, new: merge_lists(new, old),
|
2024-05-10 15:51:13 -07:00
|
|
|
},
|
|
|
|
)
|