From 1d370f56d5a7b21fc440e90944f69627fd6cedb4 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 11 Nov 2024 01:12:29 +0800 Subject: HALF WORK: 2024.12.12 --- tools/cru-py/cru/__init__.py | 4 +- tools/cru-py/cru/_base.py | 112 +++++++++++++++++++++++++++++++++++++++++ tools/cru-py/cru/_const.py | 2 +- tools/cru-py/cru/_cru.py | 94 ---------------------------------- tools/cru-py/cru/_decorator.py | 2 +- tools/cru-py/cru/_func.py | 12 ++--- tools/cru-py/cru/_iter.py | 96 ++++++++++++++++++++++------------- tools/cru-py/cru/_list.py | 104 +++++++++++++++++++------------------- tools/cru-py/cru/_type.py | 51 +++++++++++++------ 9 files changed, 270 insertions(+), 207 deletions(-) create mode 100644 tools/cru-py/cru/_base.py delete mode 100644 tools/cru-py/cru/_cru.py (limited to 'tools/cru-py') diff --git a/tools/cru-py/cru/__init__.py b/tools/cru-py/cru/__init__.py index 2ae241e..94d0d69 100644 --- a/tools/cru-py/cru/__init__.py +++ b/tools/cru-py/cru/__init__.py @@ -1,7 +1,9 @@ import sys +from ._base import CruException -class CruInitError(Exception): + +class CruInitError(CruException): pass diff --git a/tools/cru-py/cru/_base.py b/tools/cru-py/cru/_base.py new file mode 100644 index 0000000..96466d1 --- /dev/null +++ b/tools/cru-py/cru/_base.py @@ -0,0 +1,112 @@ +from typing import Any, NoReturn + +from ._lang import remove_none + + +class CruException(Exception): + """Base exception class of all exceptions in cru.""" + + +class CruNamespaceError(CruException): + """Raised when a namespace is not found.""" + + +class CruUnreachableError(CruException): + """Raised when a code path is unreachable.""" + + +def cru_unreachable() -> NoReturn: + raise CruUnreachableError() + + +class _Cru: + NAME_PREFIXES = ("CRU_", "Cru", "cru_") + + def __init__(self) -> None: + self._d: dict[str, Any] = {} + + def all_names(self) -> list[str]: + return list(self._d.keys()) + + def get(self, name: str) -> Any: + return self._d[name] + + def has_name(self, name: str) -> bool: + return name in self._d + + @staticmethod + def _maybe_remove_prefix(name: str) -> str | None: + for prefix in _Cru.NAME_PREFIXES: + if name.startswith(prefix): + return name[len(prefix) :] + return None + + def _check_name_exist(self, *names: str | None) -> None: + for name in names: + if name is None: + continue + if self.has_name(name): + raise CruNamespaceError(f"Name {name} exists in CRU.") + + @staticmethod + def check_name_format(name: str) -> tuple[str, str]: + no_prefix_name = _Cru._maybe_remove_prefix(name) + if no_prefix_name is None: + raise ValueError( + f"Name {name} is not prefixed with any of {_Cru.NAME_PREFIXES}." + ) + return name, no_prefix_name + + @staticmethod + def _check_object_name(o) -> tuple[str, str]: + return _Cru.check_name_format(o.__name__) + + def _do_add(self, o, *names: str | None) -> list[str]: + name_list: list[str] = remove_none(names) + for name in name_list: + self._d[name] = o + return name_list + + def add(self, o, name: str | None) -> tuple[str, str | None]: + no_prefix_name: str | None + if name is None: + name, no_prefix_name = self._check_object_name(o) + else: + no_prefix_name = self._maybe_remove_prefix(name) + + self._check_name_exist(name, no_prefix_name) + self._do_add(o, name, no_prefix_name) + return name, no_prefix_name + + def add_with_alias(self, o, name: str | None = None, *aliases: str) -> list[str]: + final_names: list[str | None] = [] + no_prefix_name: str | None + if name is None: + name, no_prefix_name = self._check_object_name(o) + self._check_name_exist(name, no_prefix_name) + final_names.extend([name, no_prefix_name]) + for alias in aliases: + no_prefix_name = self._maybe_remove_prefix(alias) + self._check_name_exist(alias, no_prefix_name) + final_names.extend([alias, no_prefix_name]) + + return self._do_add(o, *final_names) + + def add_objects(self, *objects): + final_list = [] + for o in objects: + name, no_prefix_name = self._check_object_name(o) + self._check_name_exist(name, no_prefix_name) + final_list.append((o, name, no_prefix_name)) + for o, name, no_prefix_name in final_list: + self._do_add(o, name, no_prefix_name) + + def __getitem__(self, item): + return self.get(item) + + def __getattr__(self, item): + return self.get(item) + + +CRU_NAME_PREFIXES = _Cru.NAME_PREFIXES +CRU = _Cru() diff --git a/tools/cru-py/cru/_const.py b/tools/cru-py/cru/_const.py index bc02c3a..8246b35 100644 --- a/tools/cru-py/cru/_const.py +++ b/tools/cru-py/cru/_const.py @@ -1,7 +1,7 @@ from enum import Enum, auto from typing import Self, TypeGuard, TypeVar -from ._cru import CRU +from ._base import CRU _T = TypeVar("_T") diff --git a/tools/cru-py/cru/_cru.py b/tools/cru-py/cru/_cru.py deleted file mode 100644 index 0085a80..0000000 --- a/tools/cru-py/cru/_cru.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Any - -from ._lang import remove_none - - -class _Cru: - NAME_PREFIXES = ("CRU_", "Cru", "cru_") - - def __init__(self) -> None: - self._d: dict[str, Any] = {} - - def all_names(self) -> list[str]: - return list(self._d.keys()) - - def get(self, name: str) -> Any: - return self._d[name] - - def has_name(self, name: str) -> bool: - return name in self._d - - @staticmethod - def _maybe_remove_prefix(name: str) -> str | None: - for prefix in _Cru.NAME_PREFIXES: - if name.startswith(prefix): - return name[len(prefix) :] - return None - - def _check_name_exist(self, *names: str | None) -> None: - for name in names: - if name is None: - continue - if self.has_name(name): - raise ValueError(f"Name {name} exists in CRU.") - - @staticmethod - def check_name_format(name: str) -> tuple[str, str]: - no_prefix_name = _Cru._maybe_remove_prefix(name) - if no_prefix_name is None: - raise ValueError(f"Name {name} is not prefixed with {_Cru.NAME_PREFIXES}.") - return name, no_prefix_name - - @staticmethod - def _check_object_name(o) -> tuple[str, str]: - return _Cru.check_name_format(o.__name__) - - def _do_add(self, o, *names: str | None) -> list[str]: - name_list: list[str] = remove_none(names) - for name in name_list: - self._d[name] = o - return name_list - - def add(self, o, name: str | None) -> tuple[str, str | None]: - no_prefix_name: str | None - if name is None: - name, no_prefix_name = self._check_object_name(o) - else: - no_prefix_name = self._maybe_remove_prefix(name) - - self._check_name_exist(name, no_prefix_name) - self._do_add(o, name, no_prefix_name) - return name, no_prefix_name - - def add_with_alias(self, o, name: str | None = None, *aliases: str) -> list[str]: - final_names: list[str | None] = [] - no_prefix_name: str | None - if name is None: - name, no_prefix_name = self._check_object_name(o) - self._check_name_exist(name, no_prefix_name) - final_names.extend([name, no_prefix_name]) - for alias in aliases: - no_prefix_name = self._maybe_remove_prefix(alias) - self._check_name_exist(alias, no_prefix_name) - final_names.extend([alias, no_prefix_name]) - - return self._do_add(o, *final_names) - - def add_objects(self, *objects): - final_list = [] - for o in objects: - name, no_prefix_name = self._check_object_name(o) - self._check_name_exist(name, no_prefix_name) - final_list.append((o, name, no_prefix_name)) - for o, name, no_prefix_name in final_list: - self._do_add(o, name, no_prefix_name) - - def __getitem__(self, item): - return self.get(item) - - def __getattr__(self, item): - return self.get(item) - - -CRU_NAME_PREFIXES = _Cru.NAME_PREFIXES -CRU = _Cru() diff --git a/tools/cru-py/cru/_decorator.py b/tools/cru-py/cru/_decorator.py index 432ceca..137fc05 100644 --- a/tools/cru-py/cru/_decorator.py +++ b/tools/cru-py/cru/_decorator.py @@ -9,7 +9,7 @@ from typing import ( cast, ) -from ._cru import CRU +from ._base import CRU _P = ParamSpec("_P") _T = TypeVar("_T") diff --git a/tools/cru-py/cru/_func.py b/tools/cru-py/cru/_func.py index 1b437be..2b317ab 100644 --- a/tools/cru-py/cru/_func.py +++ b/tools/cru-py/cru/_func.py @@ -13,7 +13,7 @@ from typing import ( ) -from ._cru import CRU +from ._base import CRU from ._const import CruPlaceholder _P = ParamSpec("_P") @@ -163,11 +163,11 @@ class _Creators: class CruFunction: - RawBase = _RawBase - Base = _Base - Creators = _Creators - Wrapper = _Wrapper - Decorators = _Dec + RawBase: TypeAlias = _RawBase + Base: TypeAlias = _Base + Creators: TypeAlias = _Creators + Wrapper: TypeAlias = _Wrapper + Decorators: TypeAlias = _Dec CRU.add_objects(CruFunction) diff --git a/tools/cru-py/cru/_iter.py b/tools/cru-py/cru/_iter.py index 83a9513..bf1dfde 100644 --- a/tools/cru-py/cru/_iter.py +++ b/tools/cru-py/cru/_iter.py @@ -18,7 +18,7 @@ from typing import ( cast, ) -from ._cru import CRU +from ._base import CRU, cru_unreachable from ._const import CruNotFound _P = ParamSpec("_P") @@ -238,7 +238,7 @@ class _Generic: pass except StopIteration as stop: return stop.value - raise RuntimeError("Should not reach here") + cru_unreachable() class _Helper: @@ -255,38 +255,49 @@ class _Helper: return wrapper -class _Dec: - - @staticmethod - def wrap(origin: Callable[_P, Iterable[_T]]) -> Callable[_P, _Wrapper[_T]]: - def _wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _Wrapper[_T]: - return _Wrapper(origin(*args, **kwargs)) - - return _wrapped - - class _Creators: + @staticmethod - @_Dec.wrap - def empty() -> Iterable[Never]: - return iter([]) + def empty( + *, + wrapper_type: type["_Wrapper[Never]"] | None = None, + attr: dict[str, Any] | None = None, + ) -> _Wrapper[Never]: + wrapper_type = wrapper_type or _Wrapper + return wrapper_type(iter([]), attr) @staticmethod - @_Dec.wrap - def range(*args) -> Iterable[int]: - return iter(range(*args)) + def range( + *args, + wrapper_type: type["_Wrapper[int]"] | None = None, + attr: dict[str, Any] | None = None, + ) -> _Wrapper[int]: + wrapper_type = wrapper_type or _Wrapper + return wrapper_type(iter(range(*args)), attr) @staticmethod - @_Dec.wrap - def unite(*args: _O) -> Iterable[_O]: - return iter(args) + def unite( + *args: _O, + wrapper_type: type["_Wrapper[_O]"] | None = None, + attr: dict[str, Any] | None = None, + ) -> _Wrapper[_O]: + wrapper_type = wrapper_type or _Wrapper + return wrapper_type(iter(args), attr) @staticmethod - @_Dec.wrap - def concat(*iterables: Iterable[_T]) -> Iterable[_T]: + def _concat(*iterables: Iterable[_T]) -> Iterable[_T]: for iterable in iterables: yield from iterable + @staticmethod + def concat( + *iterables: Iterable[_T], + wrapper_type: type["_Wrapper[_T]"] | None = None, + attr: dict[str, Any] | None = None, + ) -> _Wrapper[_T]: + wrapper_type = wrapper_type or _Wrapper + return wrapper_type(_Creators._concat(*iterables), attr) + class _Wrapper(Generic[_T]): ElementOperation: TypeAlias = Callable[[_V], Any] @@ -297,10 +308,10 @@ class _Wrapper(Generic[_T]): AnyElementTransformer: TypeAlias = ElementTransformer[Any, Any] def __init__( - self, - iterable: Iterable[_T], + self, iterable: Iterable[_T], attr: dict[str, Any] | None = None ) -> None: self._iterable = iterable + self._attr = attr or {} def __iter__(self) -> Iterator[_T]: return self._iterable.__iter__() @@ -309,23 +320,39 @@ class _Wrapper(Generic[_T]): def me(self) -> Iterable[_T]: return self._iterable - _wrap = _Dec.wrap + @property + def my_attr(self) -> dict[str, Any]: + return self._attr + + def create_with_me(self, iterable: Iterable[_O]) -> _Wrapper[_O]: + return type(self)(iterable, self._attr) # type: ignore + + @staticmethod + def _wrap( + f: Callable[Concatenate[_Wrapper[_T], _P], Iterable[_O]] + ) -> Callable[Concatenate[_Wrapper[_T], _P], _Wrapper[_O]]: + def _wrapped( + self: _Wrapper[_T], *args: _P.args, **kwargs: _P.kwargs + ) -> _Wrapper[_O]: + return self.create_with_me(f(self, *args, **kwargs)) + + return _wrapped @_wrap def replace_me(self, iterable: Iterable[_O]) -> Iterable[_O]: return iterable def replace_me_with_empty(self) -> _Wrapper[Never]: - return _Creators.empty() + return _Creators.empty(wrapper_type=type(self), attr=self._attr) # type: ignore def replace_me_with_range(self, *args) -> _Wrapper[int]: - return _Creators.range(*args) + return _Creators.range(*args, attr=self._attr) def replace_me_with_unite(self, *args: _O) -> _Wrapper[_O]: - return _Creators.unite(*args) + return _Creators.unite(*args, attr=self._attr) def replace_me_with_concat(self, *iterables: Iterable[_T]) -> _Wrapper[_T]: - return _Creators.concat(*iterables) + return _Creators.concat(*iterables, attr=self._attr) def to_set(self, discard: Iterable[Any]) -> set[_T]: return set(self.me) - set(discard) @@ -422,11 +449,10 @@ class _Wrapper(Generic[_T]): class CruIterable: - Decorators = _Dec - Generic = _Generic - Helper = _Helper - Creators = _Creators - Wrapper = _Wrapper + Generic: TypeAlias = _Generic + Helper: TypeAlias = _Helper + Creators: TypeAlias = _Creators + Wrapper: TypeAlias = _Wrapper CRU.add_objects(CruIterable, _Wrapper) diff --git a/tools/cru-py/cru/_list.py b/tools/cru-py/cru/_list.py index f1965db..45122bf 100644 --- a/tools/cru-py/cru/_list.py +++ b/tools/cru-py/cru/_list.py @@ -1,77 +1,75 @@ +from __future__ import annotations +from collections.abc import Callable +from typing import Generic, Iterable, Self, TypeAlias, TypeVar +from ._iter import CruIterable -class CruInplaceList(CruList, Generic[_V]): +_T = TypeVar("_T") - def clear(self) -> "CruInplaceList[_V]": - self.clear() - return self - def extend(self, *l: Iterable[_V]) -> "CruInplaceList[_V]": - self.extend(l) - return self +class CruListEdit(CruIterable.Wrapper[_T]): + def done(self) -> CruList[_T]: + l: CruList[_T] = self.my_attr["list"] + l.reset(self) + return l - def reset(self, *l: Iterable[_V]) -> "CruInplaceList[_V]": + +class CruList(list[_T]): + def reset(self, new_values: Iterable[_T]): + if self is new_values: + new_values = list(new_values) self.clear() - self.extend(l) + self.extend(new_values) return self - def transform(self, *f: OptionalElementTransformer) -> "CruInplaceList"[Any]: - return self.reset(super().transform(*f)) - - def transform_if( - self, f: OptionalElementTransformer, p: ElementPredicate[_V] - ) -> "CruInplaceList"[Any]: - return self.reset(super().transform_if(f, p)) + def as_iterable_wrapper(self) -> CruIterable.Wrapper[_T]: + return CruIterable.Wrapper(self) - def remove_by_indices(self, *index: int) -> "CruInplaceList"[_V]: - return self.reset(super().remove_by_indices(*index)) - - def remove_all_if(self, p: ElementPredicate[_V]) -> "CruInplaceList"[_V]: - return self.reset(super().remove_all_if(p)) - - def remove_all_value(self, *r: Any) -> "CruInplaceList"[_V]: - return self.reset(super().remove_all_value(*r)) - - def replace_all_value( - self, old_value: Any, new_value: R - ) -> "CruInplaceList"[_V | R]: - return self.reset(super().replace_all_value(old_value, new_value)) + def as_edit_iterable_wrapper(self) -> CruListEdit[_T]: + return CruListEdit(self, attr={"list": self}) @staticmethod - def make(l: CanBeList[_V]) -> "CruInplaceList"[_V]: - return CruInplaceList(ListOperations.make(l)) - + def make(maybe_list: Iterable[_T] | _T | None) -> CruList[_T]: + if maybe_list is None: + return CruList() + if isinstance(maybe_list, Iterable): + return CruList(maybe_list) + return CruList([maybe_list]) -K = TypeVar("K") +_K = TypeVar("_K") +_KeyGetter: TypeAlias = Callable[[_T], _K] -class CruUniqueKeyInplaceList(Generic[_V, K]): - KeyGetter = Callable[[_V], K] +class CruUniqueKeyList(Generic[_T, _K]): def __init__( - self, get_key: KeyGetter, *, before_add: Callable[[_V], _V] | None = None + self, + key_getter: _KeyGetter[_T, _K], + *, + before_add: Callable[[_T], _T] | None = None, ): super().__init__() - self._get_key = get_key + self._key_getter = key_getter self._before_add = before_add - self._l: CruInplaceList[_V] = CruInplaceList() + self._list: CruList[_T] = CruList() @property - def object_key_getter(self) -> KeyGetter: - return self._get_key + def key_getter(self) -> _KeyGetter[_T, _K]: + return self._key_getter @property - def internal_list(self) -> CruInplaceList[_V]: - return self._l + def internal_list(self) -> CruList[_T]: + return self._list def validate_self(self): - keys = self._l.transform(self._get_key) + keys = self._list.transform(self._key_getter) if len(keys) != len(set(keys)): raise ValueError("Duplicate keys!") - def get_or(self, k: K, fallback: Any = CRU_NOT_FOUND) -> _V | Any: - r = self._l.find_if(lambda i: k == self._get_key(i)) + # TODO: Continue here! + def get_or(self, key: K, fallback: Any = CRU_NOT_FOUND) -> _V | Any: + r = self._l.find_if(lambda i: k == self._key_getter(i)) return r if r is not CRU_NOT_FOUND else fallback def get(self, k: K) -> _V: @@ -84,10 +82,10 @@ class CruUniqueKeyInplaceList(Generic[_V, K]): return self.get_or(k, CRU_NOT_FOUND) is not CRU_NOT_FOUND def has_any_key(self, *k: K) -> bool: - return self._l.any(lambda i: self._get_key(i) in k) + return self._l.any(lambda i: self._key_getter(i) in k) def try_remove(self, k: K) -> bool: - i = self._l.find_index_if(lambda v: k == self._get_key(v)) + i = self._l.find_index_if(lambda v: k == self._key_getter(v)) if i is CRU_NOT_FOUND: return False self._l.remove_by_indices(i) @@ -98,11 +96,11 @@ class CruUniqueKeyInplaceList(Generic[_V, K]): raise KeyError(f"Key {k} not found!") def add(self, v: _V, /, replace: bool = False) -> None: - if self.has_key(self._get_key(v)): + if self.has_key(self._key_getter(v)): if replace: - self.remove(self._get_key(v)) + self.remove(self._key_getter(v)) else: - raise ValueError(f"Key {self._get_key(v)} already exists!") + raise ValueError(f"Key {self._key_getter(v)} already exists!") if self._before_add is not None: v = self._before_add(v) self._l.append(v) @@ -111,12 +109,12 @@ class CruUniqueKeyInplaceList(Generic[_V, K]): self.add(v, True) def extend(self, l: Iterable[_V], /, replace: bool = False) -> None: - if not replace and self.has_any_key([self._get_key(i) for i in l]): + if not replace and self.has_any_key([self._key_getter(i) for i in l]): raise ValueError("Keys already exists!") if self._before_add is not None: l = [self._before_add(i) for i in l] - keys = [self._get_key(i) for i in l] - self._l.remove_all_if(lambda i: self._get_key(i) in keys).extend(l) + keys = [self._key_getter(i) for i in l] + self._l.remove_all_if(lambda i: self._key_getter(i) in keys).extend(l) def clear(self) -> None: self._l.clear() diff --git a/tools/cru-py/cru/_type.py b/tools/cru-py/cru/_type.py index b67ee9a..c60a913 100644 --- a/tools/cru-py/cru/_type.py +++ b/tools/cru-py/cru/_type.py @@ -1,26 +1,31 @@ from types import NoneType from typing import Any -from ._iter import CanBeList, CruList - DEFAULT_NONE_ERR = ValueError DEFAULT_NONE_ERR_MSG = "None is not allowed here." DEFAULT_TYPE_ERR = ValueError DEFAULT_TYPE_ERR_MSG = "Type of object is not allowed here." -class TypeSet(set[type]): - def __init__(self, *l: type): +class TypeSet: + def __init__(self, *types: type): l = CruList.make(l).remove_all_value(None, NoneType) if not l.all_is_instance(type): raise TypeError("t must be a type or None.") super().__init__(l) - def check_value(self, v: Any, /, allow_none: bool, empty_allow_all: bool = True, *, - none_err: type[Exception] = DEFAULT_NONE_ERR, - none_err_msg: str = DEFAULT_NONE_ERR_MSG, - type_err: type[Exception] = DEFAULT_TYPE_ERR, - type_err_msg: str = DEFAULT_TYPE_ERR_MSG) -> None: + def check_value( + self, + v: Any, + /, + allow_none: bool, + empty_allow_all: bool = True, + *, + none_err: type[Exception] = DEFAULT_NONE_ERR, + none_err_msg: str = DEFAULT_NONE_ERR_MSG, + type_err: type[Exception] = DEFAULT_TYPE_ERR, + type_err_msg: str = DEFAULT_TYPE_ERR_MSG, + ) -> None: if v is None: if allow_none: return @@ -31,12 +36,26 @@ class TypeSet(set[type]): if type(v) not in self: raise type_err(type_err_msg) - def check_value_list(self, l: CanBeList, /, allow_none: bool, empty_allow_all: bool = True, *, - none_err: type[Exception] = DEFAULT_NONE_ERR, - none_err_msg: str = DEFAULT_NONE_ERR_MSG, - type_err: type[Exception] = DEFAULT_TYPE_ERR, - type_err_msg: str = DEFAULT_TYPE_ERR_MSG) -> None: + def check_value_list( + self, + l: CanBeList, + /, + allow_none: bool, + empty_allow_all: bool = True, + *, + none_err: type[Exception] = DEFAULT_NONE_ERR, + none_err_msg: str = DEFAULT_NONE_ERR_MSG, + type_err: type[Exception] = DEFAULT_TYPE_ERR, + type_err_msg: str = DEFAULT_TYPE_ERR_MSG, + ) -> None: l = CruList.make(l) for v in l: - self.check_value(v, allow_none, empty_allow_all, none_err=none_err, none_err_msg=none_err_msg, - type_err=type_err, type_err_msg=type_err_msg) + self.check_value( + v, + allow_none, + empty_allow_all, + none_err=none_err, + none_err_msg=none_err_msg, + type_err=type_err, + type_err_msg=type_err_msg, + ) -- cgit v1.2.3