aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/util
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cru-py/cru/util')
-rw-r--r--tools/cru-py/cru/util/__init__.py16
-rw-r--r--tools/cru-py/cru/util/_const.py51
-rw-r--r--tools/cru-py/cru/util/_func.py46
-rw-r--r--tools/cru-py/cru/util/_list.py272
4 files changed, 304 insertions, 81 deletions
diff --git a/tools/cru-py/cru/util/__init__.py b/tools/cru-py/cru/util/__init__.py
index 83ab9f3..2cbb8f4 100644
--- a/tools/cru-py/cru/util/__init__.py
+++ b/tools/cru-py/cru/util/__init__.py
@@ -1,5 +1,8 @@
-from ._func import CruFunction, MetaFunction, RawFunctions, WrappedFunctions, PLACEHOLDER
-from ._list import CruList, ListOperations, CanBeList
+from ._const import make_unique_object, make_bool_unique_object, CRU_NOT_FOUND, CRU_USE_DEFAULT, CRU_DONT_CHANGE, \
+ CRU_PLACEHOLDER
+from ._func import CruFunction, MetaFunction, RawFunctions, WrappedFunctions
+from ._list import CruList, CruInplaceList, CruUniqueKeyInplaceList, ListOperations, CanBeList, ElementOperation, \
+ ElementPredicate, ElementTransformer, OptionalElementOperation, ElementPredicate, OptionalElementTransformer
from ._type import TypeSet
F = CruFunction
@@ -7,8 +10,11 @@ WF = WrappedFunctions
L = CruList
__all__ = [
- "CruFunction", "MetaFunction", "RawFunctions", "WrappedFunctions", "PLACEHOLDER",
- "CruList", "ListOperations", "CanBeList",
+ "CRU_NOT_FOUND", "CRU_USE_DEFAULT", "CRU_DONT_CHANGE", "CRU_PLACEHOLDER",
+ "CruFunction", "MetaFunction", "RawFunctions", "WrappedFunctions",
+ "CruList", "CruInplaceList", "CruUniqueKeyInplaceList", "ListOperations",
+ "CanBeList", "ElementOperation", "ElementPredicate", "ElementTransformer",
+ "OptionalElementOperation", "ElementPredicate", "OptionalElementTransformer",
"TypeSet",
- "F", "WF", "L",
+ "F", "WF", "L"
]
diff --git a/tools/cru-py/cru/util/_const.py b/tools/cru-py/cru/util/_const.py
new file mode 100644
index 0000000..ea67450
--- /dev/null
+++ b/tools/cru-py/cru/util/_const.py
@@ -0,0 +1,51 @@
+from typing import Any
+
+
+def make_unique_object() -> Any:
+ class _CruUnique:
+ _i = False
+
+ def __init__(self):
+ if self._i:
+ raise ValueError("_CruAttrNotSet is a singleton!")
+ self._i = True
+
+ def __copy__(self):
+ return self
+
+ def __eq__(self, other):
+ return isinstance(other, _CruUnique)
+
+ v = _CruUnique()
+
+ return v
+
+
+def make_bool_unique_object(b: bool) -> Any:
+ class _CruBoolUnique:
+ _i = False
+
+ def __init__(self):
+ super().__init__(b)
+ if self._i:
+ raise ValueError("_CruAttrNotSet is a singleton!")
+ self._i = True
+
+ def __copy__(self):
+ return self
+
+ def __eq__(self, other):
+ return isinstance(other, _CruBoolUnique) or b == other
+
+ def __bool__(self):
+ return b
+
+ v = _CruBoolUnique()
+
+ return v
+
+
+CRU_NOT_FOUND = make_bool_unique_object(False)
+CRU_USE_DEFAULT = make_unique_object()
+CRU_DONT_CHANGE = make_unique_object()
+CRU_PLACEHOLDER = make_unique_object()
diff --git a/tools/cru-py/cru/util/_func.py b/tools/cru-py/cru/util/_func.py
index 5fb49a9..3221c94 100644
--- a/tools/cru-py/cru/util/_func.py
+++ b/tools/cru-py/cru/util/_func.py
@@ -1,7 +1,7 @@
-from collections.abc import Callable, Iterable
+from collections.abc import Callable
from typing import TypeVar, Any, ParamSpec
-from ._list import ListOperations, CruList
+from ._const import CRU_PLACEHOLDER
T = TypeVar("T")
R = TypeVar("R")
@@ -10,35 +10,28 @@ P = ParamSpec("P")
P1 = ParamSpec("P1")
-class _Placeholder:
- pass
-
-
-PLACEHOLDER = _Placeholder()
-
-
class RawFunctions:
@staticmethod
- def ignore(*_v, **_kwargs) -> None:
+ def none(*_v, **_kwargs) -> None:
return None
@staticmethod
- def true_(*_v, **_kwargs) -> True:
+ def true(*_v, **_kwargs) -> True:
return True
@staticmethod
- def false_(*_v, **_kwargs) -> False:
+ def false(*_v, **_kwargs) -> False:
return False
@staticmethod
- def i_dont_care(r: T, *_v, **_kwargs) -> T:
- return r
-
- @staticmethod
def identity(v: T) -> T:
return v
@staticmethod
+ def only_you(r: T, *_v, **_kwargs) -> T:
+ return r
+
+ @staticmethod
def equal(a: Any, b: Any) -> bool:
return a == b
@@ -58,7 +51,7 @@ class MetaFunction:
popped = 0
real_args = []
for a in bind_args:
- if isinstance(a, _Placeholder):
+ if a is CRU_PLACEHOLDER:
real_args.append(args[popped])
popped += 1
else:
@@ -129,22 +122,17 @@ class CruFunction:
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
- def list_transform(self, l: Iterable[T]) -> CruList[T]:
- return CruList(l).transform(self)
-
- def list_all(self, l: Iterable[T]) -> bool:
- return ListOperations.all(l, self)
-
- def list_any(self, l: Iterable[T]) -> bool:
- return ListOperations.any(l, self)
-
- def list_remove_all_if(self, l: Iterable[T]) -> CruList[T]:
- return CruList(ListOperations.remove_all_if(l, self))
+ @staticmethod
+ def make_chain(*fs: Callable) -> Callable[P, R1]:
+ return CruFunction(MetaFunction.chain(*fs))
class WrappedFunctions:
+ none = CruFunction(RawFunctions.none)
+ true = CruFunction(RawFunctions.true)
+ false = CruFunction(RawFunctions.false)
identity = CruFunction(RawFunctions.identity)
- ignore = CruFunction(RawFunctions.ignore)
+ only_you = CruFunction(RawFunctions.only_you)
equal = CruFunction(RawFunctions.equal)
not_equal = CruFunction(RawFunctions.not_equal)
not_ = CruFunction(RawFunctions.not_)
diff --git a/tools/cru-py/cru/util/_list.py b/tools/cru-py/cru/util/_list.py
index 3b4cb28..92cd88c 100644
--- a/tools/cru-py/cru/util/_list.py
+++ b/tools/cru-py/cru/util/_list.py
@@ -1,17 +1,32 @@
from collections.abc import Iterable, Callable
from typing import TypeVar, ParamSpec, Any, Generic
+from ._const import CRU_NOT_FOUND
+
T = TypeVar("T")
R = TypeVar("R")
P = ParamSpec("P")
CanBeList = T | Iterable[T] | None
+ElementOperation = Callable[[T], Any]
+ElementPredicate = Callable[[T], bool]
+ElementTransformer = Callable[[Any], Any]
+OptionalElementOperation = Callable[[T], Any] | None
+OptionalElementTransformer = Callable[[Any], Any] | None
class ListOperations:
@staticmethod
- def foreach(l: Iterable[T], *f: Callable[[T], None] | None) -> None:
+ def sub_by_indices(l: Iterable[T], *index: int) -> list[T]:
+ return [v for i, v in enumerate(l) if i in index]
+
+ @staticmethod
+ def complement_indices(length: int, *index: int) -> list[int]:
+ return [i for i in range(length) if i not in index]
+
+ @staticmethod
+ def foreach(l: Iterable[T], *f: OptionalElementOperation[T]) -> None:
if len(f) == 0: return
for v in l:
for f_ in f:
@@ -19,88 +34,131 @@ class ListOperations:
f_(v)
@staticmethod
- def transform(l: Iterable[T], *f: Callable | None) -> list[R]:
- r = []
+ def all(l: Iterable[T], p: ElementPredicate[T]) -> bool:
for v in l:
- for f_ in f:
- if f_ is not None:
- v = f_(v)
- r.append(v)
- return r
+ if not p(v): return False
+ return True
@staticmethod
- def transform_if(l: Iterable[T], f: Callable[[T], R] | None, p: Callable[[T], bool] | None) -> list[R]:
- return [(f(v) if f else v) for v in l if (p(v) if p else True)]
+ def all_is_instance(l: Iterable[T], *t: type) -> bool:
+ return all(type(v) in t for v in l)
@staticmethod
- def all(l: Iterable[T], *p: Callable[[T], bool] | None) -> bool:
- if len(p) == 0 or all(v is None for v in p):
- raise ValueError("At least one predicate is required!")
+ def any(l: Iterable[T], p: ElementPredicate[T]) -> bool:
for v in l:
- for p_ in p:
- if p_ is not None and not p_(v): return False
- return True
+ if p(v): return True
+ return False
@staticmethod
- def all_is_instance(l: Iterable[T], *t: type) -> bool:
- return all(type(v) in t for v in l)
+ def find_all_if(l: Iterable[T], p: ElementPredicate[T]) -> list[T]:
+ return [v for v in l if p(v)]
+
+ @staticmethod
+ def find_if(l: Iterable[T], p: ElementPredicate[T]) -> T | CRU_NOT_FOUND:
+ r = ListOperations.find_all_if(l, p)
+ return r[0] if len(r) > 0 else CRU_NOT_FOUND
+
+ @staticmethod
+ def find_all_indices_if(l: Iterable[T], p: ElementPredicate[T]) -> list[int]:
+ return [i for i, v in enumerate(l) if p(v)]
@staticmethod
- def any(l: Iterable[T], *p: Callable[[T], bool] | None) -> bool:
- if len(p) == 0 or all(v is None for v in p):
- raise ValueError("At least one predicate is required!")
+ def find_index_if(l: Iterable[T], p: ElementPredicate[T]) -> int | CRU_NOT_FOUND:
+ r = ListOperations.find_all_indices_if(l, p)
+ return r[0] if len(r) > 0 else CRU_NOT_FOUND
+
+ @staticmethod
+ def transform(l: Iterable[T], *f: OptionalElementTransformer) -> list[Any]:
+ r = []
for v in l:
- for p_ in p:
- if p_ is not None and p_(v): return True
- return False
+ for f_ in f:
+ if f_ is not None:
+ v = f_(v)
+ r.append(v)
+ return r
@staticmethod
- def make(v: CanBeList[T], /, none_to_empty_list: bool = True) -> list[T]:
- if v is None and none_to_empty_list: return []
- return v if isinstance(v, Iterable) else [v]
+ def transform_if(l: Iterable[T], f: OptionalElementTransformer, p: ElementPredicate[T]) -> list[Any]:
+ return [(f(v) if f else v) for v in l if p(v)]
@staticmethod
- def remove_all_if(l: Iterable[T], *p: Callable[[], bool] | None) -> list[T]:
- def stay(v):
- for p_ in p:
- if p_ is not None and p_(): return False
- return True
+ def remove_by_indices(l: Iterable[T], *index: int | None) -> list[T]:
+ return [v for i, v in enumerate(l) if i not in index]
- return [v for v in l if stay(v)]
+ @staticmethod
+ def remove_if(l: Iterable[T], p: ElementPredicate[T]) -> list[T]:
+ i = ListOperations.find_index_if(l, p)
+ return ListOperations.remove_by_indices(l, i)
+
+ @staticmethod
+ def remove_all_if(l: Iterable[T], p: ElementPredicate[T]) -> list[T]:
+ return [v for v in l if not p(v)]
@staticmethod
- def remove_all_value(l: Iterable[T], *r) -> list[T]:
+ def remove_all_value(l: Iterable[T], *r: Any) -> list[T]:
return [v for v in l if v not in r]
@staticmethod
def replace_all_value(l: Iterable[T], old_value: Any, new_value: R) -> list[T | R]:
return [new_value if v == old_value else v for v in l]
+ @staticmethod
+ def make(v: CanBeList[T], /, none_to_empty_list: bool = True) -> list[T]:
+ if v is None and none_to_empty_list: return []
+ return list(v) if isinstance(v, Iterable) else [v]
+
class CruList(list, Generic[T]):
+ @property
+ def is_empty(self) -> bool:
+ return len(self) == 0
- def foreach(self, *f: Callable[[T], None] | None) -> None:
- ListOperations.foreach(self, *f)
+ def sub_by_indices(self, *index: int) -> "CruList"[T]:
+ return CruList(ListOperations.sub_by_indices(self, *index))
- def transform(self, *f: Callable[[T], R] | None) -> "CruList"[R]:
- return CruList(ListOperations.transform(self, *f))
+ def complement_indices(self, *index: int) -> list[int]:
+ return ListOperations.complement_indices(len(self), *index)
- def transform_if(self, f: Callable[[T], R] | None, p: Callable[[T], bool] | None) -> "CruList"[R]:
- return CruList(ListOperations.transform_if(self, f, p))
+ def foreach(self, *f: OptionalElementOperation[T]) -> None:
+ ListOperations.foreach(self, *f)
- def all(self, *p: Callable[[T], bool] | None) -> bool:
- return ListOperations.all(self, *p)
+ def all(self, p: ElementPredicate[T]) -> bool:
+ return ListOperations.all(self, p)
def all_is_instance(self, *t: type) -> bool:
return ListOperations.all_is_instance(self, *t)
- def any(self, *p: Callable[[T], bool] | None) -> bool:
- return ListOperations.any(self, *p)
+ def any(self, p: ElementPredicate[T]) -> bool:
+ return ListOperations.any(self, p)
- def remove_all_if(self, *p: Callable[[]]) -> "CruList"[T]:
- return CruList(ListOperations.remove_all_if(self, *p))
+ def find_all_if(self, p: ElementPredicate[T]) -> "CruList"[T]:
+ return CruList(ListOperations.find_all_if(self, p))
- def remove_all_value(self, *r) -> "CruList"[T]:
+ def find_if(self, p: ElementPredicate[T]) -> T | CRU_NOT_FOUND:
+ return ListOperations.find_if(self, p)
+
+ def find_all_indices_if(self, p: ElementPredicate[T]) -> "CruList"[int]:
+ return CruList(ListOperations.find_all_indices_if(self, p))
+
+ def find_index_if(self, p: ElementPredicate[T]) -> int | CRU_NOT_FOUND:
+ return ListOperations.find_index_if(self, p)
+
+ def transform(self, *f: OptionalElementTransformer) -> "CruList"[Any]:
+ return CruList(ListOperations.transform(self, *f))
+
+ def transform_if(self, f: OptionalElementTransformer, p: ElementPredicate[T]) -> "CruList"[Any]:
+ return CruList(ListOperations.transform_if(self, f, p))
+
+ def remove_by_indices(self, *index: int) -> "CruList"[T]:
+ return CruList(ListOperations.remove_by_indices(self, *index))
+
+ def remove_if(self, p: ElementPredicate[T]) -> "CruList"[T]:
+ return CruList(ListOperations.remove_if(self, p))
+
+ def remove_all_if(self, p: ElementPredicate[T]) -> "CruList"[T]:
+ return CruList(ListOperations.remove_all_if(self, p))
+
+ def remove_all_value(self, *r: Any) -> "CruList"[T]:
return CruList(ListOperations.remove_all_value(self, *r))
def replace_all_value(self, old_value: Any, new_value: R) -> "CruList"[T | R]:
@@ -109,3 +167,123 @@ class CruList(list, Generic[T]):
@staticmethod
def make(l: CanBeList[T]) -> "CruList"[T]:
return CruList(ListOperations.make(l))
+
+
+class CruInplaceList(CruList, Generic[T]):
+
+ def clear(self) -> "CruInplaceList[T]":
+ self.clear()
+ return self
+
+ def extend(self, *l: Iterable[T]) -> "CruInplaceList[T]":
+ self.extend(l)
+ return self
+
+ def reset(self, *l: Iterable[T]) -> "CruInplaceList[T]":
+ self.clear()
+ self.extend(l)
+ return self
+
+ def transform(self, *f: OptionalElementTransformer) -> "CruInplaceList"[Any]:
+ return self.reset(super().transform(*f))
+
+ def transform_if(self, f: OptionalElementTransformer, p: ElementPredicate[T]) -> "CruInplaceList"[Any]:
+ return self.reset(super().transform_if(f, p))
+
+ def remove_by_indices(self, *index: int) -> "CruInplaceList"[T]:
+ return self.reset(super().remove_by_indices(*index))
+
+ def remove_all_if(self, p: ElementPredicate[T]) -> "CruInplaceList"[T]:
+ return self.reset(super().remove_all_if(p))
+
+ def remove_all_value(self, *r: Any) -> "CruInplaceList"[T]:
+ return self.reset(super().remove_all_value(*r))
+
+ def replace_all_value(self, old_value: Any, new_value: R) -> "CruInplaceList"[T | R]:
+ return self.reset(super().replace_all_value(old_value, new_value))
+
+ @staticmethod
+ def make(l: CanBeList[T]) -> "CruInplaceList"[T]:
+ return CruInplaceList(ListOperations.make(l))
+
+
+K = TypeVar("K")
+
+
+class CruUniqueKeyInplaceList(Generic[T, K]):
+ KeyGetter = Callable[[T], K]
+
+ def __init__(self, get_key: KeyGetter, *, before_add: Callable[[T], T] | None = None):
+ super().__init__()
+ self._get_key = get_key
+ self._before_add = before_add
+ self._l: CruInplaceList[T] = CruInplaceList()
+
+ @property
+ def object_key_getter(self) -> KeyGetter:
+ return self._get_key
+
+ @property
+ def internal_list(self) -> CruInplaceList[T]:
+ return self._l
+
+ def validate_self(self):
+ keys = self._l.transform(self._get_key)
+ if len(keys) != len(set(keys)):
+ raise ValueError("Duplicate keys!")
+
+ def get_or(self, k: K, fallback: Any = CRU_NOT_FOUND) -> T | Any:
+ r = self._l.find_if(lambda i: k == self._get_key(i))
+ return r if r is not CRU_NOT_FOUND else fallback
+
+ def get(self, k: K) -> T:
+ v = self.get_or(k, CRU_NOT_FOUND)
+ if v is CRU_NOT_FOUND:
+ raise KeyError(f"Key not found!")
+ return v
+
+ def has_key(self, k: K) -> bool:
+ 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)
+
+ def try_remove(self, k: K) -> bool:
+ i = self._l.find_index_if(lambda v: k == self._get_key(v))
+ if i is CRU_NOT_FOUND: return False
+ self._l.remove_by_indices(i)
+ return True
+
+ def remove(self, k: K, allow_absense: bool = False) -> None:
+ if not self.try_remove(k) and not allow_absense:
+ raise KeyError(f"Key {k} not found!")
+
+ def add(self, v: T, /, replace: bool = False) -> None:
+ if self.has_key(self._get_key(v)):
+ if replace:
+ self.remove(self._get_key(v))
+ else:
+ raise ValueError(f"Key {self._get_key(v)} already exists!")
+ if self._before_add is not None:
+ v = self._before_add(v)
+ self._l.append(v)
+
+ def set(self, v: T) -> None:
+ self.add(v, True)
+
+ def extend(self, l: Iterable[T], /, replace: bool = False) -> None:
+ if not replace and self.has_any_key([self._get_key(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)
+
+ def clear(self) -> None:
+ self._l.clear()
+
+ def __iter__(self):
+ return iter(self._l)
+
+ def __len__(self):
+ return len(self._l)