diff --git a/tests/test_predicate.py b/tests/test_predicate.py new file mode 100644 index 0000000..e2543a7 --- /dev/null +++ b/tests/test_predicate.py @@ -0,0 +1,98 @@ +import pytest + +from tibia.predicate import all_, any_, not_, when, when_or, where +from tibia.value import Value + + +@pytest.mark.parametrize( + ("value", "target"), + [ + ("", False), + ("abc", False), + ("abc123", True), + ], +) +def test_all(value: str, target: bool) -> None: + pfn = all_( + lambda s: "a" in s, + lambda s: "1" in s, + ) + + assert pfn(value) == target + + +@pytest.mark.parametrize( + ("value", "target"), + [ + ("", False), + ("abc", True), + ("abc123", True), + ], +) +def test_any(value: str, target: bool) -> None: + pfn = any_( + lambda s: "a" in s, + lambda s: "1" in s, + ) + + assert pfn(value) == target + + +@pytest.mark.parametrize( + ("value", "target", "target_not"), + [ + ("", False, True), + ("abc", True, False), + ("123", False, True), + ], +) +def test_not(value: str, target: bool, target_not: bool) -> None: + not_pnf = not_(str.__contains__, "abc") + + assert ("abc" in value) == target + assert not_pnf(value) == target_not + + +@pytest.mark.parametrize( + ("value", "target"), + [ + ("", False), + ("abc", True), + ("abc123", True), + ], +) +def test_where(value: str, target: bool) -> None: + pfn = ( + where(str.__contains__, "a") + .and_(str.__contains__, "b") + .or_(str.__contains__, "c12") + .unwrap() + ) + + assert pfn(value) == target + + +@pytest.mark.parametrize( + ("value", "target"), + [ + (0, 0), + (1, 2), + ], +) +def test_when(value: int, target: int) -> None: + result = Value(value).map(when, lambda i: i > 0, lambda i: i + 1).unwrap() + + assert result == target + + +@pytest.mark.parametrize( + ("value", "target"), + [ + (0, "no"), + (1, "1"), + ], +) +def test_when_or(value: int, target: str) -> None: + result = Value(value).map(when_or, lambda i: i > 0, "no", str).unwrap() + + assert result == target diff --git a/tibia/__init__.py b/tibia/__init__.py index d40a598..f66c1fa 100644 --- a/tibia/__init__.py +++ b/tibia/__init__.py @@ -2,6 +2,7 @@ from .future_maybe import FutureMaybe from .future_result import FutureResult from .maybe import Empty, Maybe, Some +from .predicate import all_, any_, not_, where from .result import Err, Ok, Result from .value import Value @@ -12,6 +13,10 @@ "Empty", "Maybe", "Some", + "all_", + "any_", + "not_", + "where", "Err", "Ok", "Result", diff --git a/tibia/predicate.py b/tibia/predicate.py new file mode 100644 index 0000000..3737cb0 --- /dev/null +++ b/tibia/predicate.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import Callable, Concatenate + + +def all_[T](*pfns: Callable[[T], bool]) -> Callable[[T], bool]: + return lambda t: all(fn(t) for fn in pfns) + + +def any_[T](*pfns: Callable[[T], bool]) -> Callable[[T], bool]: + return lambda t: any(fn(t) for fn in pfns) + + +def not_[T, **P]( + pfn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, +) -> Callable[[T], bool]: + return lambda t: not pfn(t, *args, **kwargs) + + +class where[T, **P]: + pfn: Callable[[T], bool] + + def __init__( + self, + pfn: Callable[Concatenate[T, P], bool], + *args: P.args, + **kwargs: P.kwargs, + ): + self.pfn = lambda t: pfn(t, *args, **kwargs) + + def __call__(self, value: T) -> bool: + return self.pfn(value) + + def or_[**P_]( + self, + pfn: Callable[Concatenate[T, P_], bool], + *args: P_.args, + **kwargs: P_.kwargs, + ) -> where[T]: + return where(lambda t: self.pfn(t) or pfn(t, *args, **kwargs)) + + def and_[**P_]( + self, + pfn: Callable[Concatenate[T, P_], bool], + *args: P_.args, + **kwargs: P_.kwargs, + ) -> where[T]: + return where(lambda t: self.pfn(t) and pfn(t, *args, **kwargs)) + + def unwrap(self) -> Callable[[T], bool]: + return self + + +def when[T, **P]( + value: T, + pfn: Callable[[T], bool], + fn: Callable[Concatenate[T, P], T], + *args: P.args, + **kwargs: P.kwargs, +) -> T: + return fn(value, *args, **kwargs) if pfn(value) else value + + +def when_or[T, **P, R]( + value: T, + pfn: Callable[[T], bool], + other: R, + fn: Callable[Concatenate[T, P], R], + *args: P.args, + **kwargs: P.kwargs, +) -> R: + return fn(value, *args, **kwargs) if pfn(value) else other