From f07d691004f51b05cf888787f7a89714476671b4 Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Fri, 19 Sep 2025 16:52:23 +0200 Subject: [PATCH] add parse_time(), validators.not_greater_than() --- CHANGELOG.md | 2 ++ src/suou/calendar.py | 28 +++++++++++++++++++++++++++- src/suou/luck.py | 3 ++- src/suou/validators.py | 14 ++++++++++++-- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2717927..5e92377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ + Add 7 new throwable exceptions + Add color utilities: `chalk` module + Add `.terminal` module, to ease TUI development. ++ `calendar`: add `parse_time()`. ++ Add validator `not_greater_than()`. ## 0.6.1 diff --git a/src/suou/calendar.py b/src/suou/calendar.py index 1733853..d2af051 100644 --- a/src/suou/calendar.py +++ b/src/suou/calendar.py @@ -18,6 +18,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import datetime from suou.functools import not_implemented +from suou.luck import lucky +from suou.validators import not_greater_than def want_isodate(d: datetime.datetime | str | float | int, *, tz = None) -> str: @@ -63,4 +65,28 @@ def age_and_days(date: datetime.datetime, now: datetime.datetime | None = None) d = (now - datetime.date(date.year + y, date.month, date.day)).days return y, d -__all__ = ('want_datetime', 'want_timestamp', 'want_isodate', 'age_and_days') \ No newline at end of file +@lucky([not_greater_than(259200)]) +def parse_time(timestr: str, /) -> int: + """ + Parse a number-suffix (es. 3s, 15m) or colon (1:30) time expression. + + Returns seconds as an integer. + """ + if timestr.isdigit(): + return int(timestr) + elif ':' in timestr: + timeparts = timestr.split(':') + if not timeparts[0].isdigit() and not all(x.isdigit() and len(x) == 2 for x in timeparts[1:]): + raise ValueError('invalid time format') + return sum(int(x) * 60 ** (len(timeparts) - 1 - i) for i, x in enumerate(timeparts)) + elif timestr.endswith('s') and timestr[:-1].isdigit(): + return int(timestr[:-1]) + elif timestr.endswith('m') and timestr[:-1].isdigit(): + return int(timestr[:-1]) * 60 + elif timestr.endswith('h') and timestr[:-1].isdigit(): + return int(float(timestr[:-1]) * 3600) + else: + raise ValueError('invalid time format') + + +__all__ = ('want_datetime', 'want_timestamp', 'want_isodate', 'age_and_days', 'parse_time') \ No newline at end of file diff --git a/src/suou/luck.py b/src/suou/luck.py index 7b8192e..04d4291 100644 --- a/src/suou/luck.py +++ b/src/suou/luck.py @@ -44,7 +44,8 @@ def lucky(validators: Iterable[Callable[[_U], bool]] = ()): for v in validators: try: if not v(result): - raise BadLuckError(f'result not expected: {result!r}') + message = 'result not expected' + raise BadLuckError(f'{message}: {result!r}') except BadLuckError: raise except Exception as e: diff --git a/src/suou/validators.py b/src/suou/validators.py index 609b99e..9fd0a3c 100644 --- a/src/suou/validators.py +++ b/src/suou/validators.py @@ -22,7 +22,7 @@ _T = TypeVar('_T') def matches(regex: str | int, /, length: int = 0, *, flags=0): """ - Return a function which returns true if X is shorter than length and matches the given regex. + Return a function which returns True if X is shorter than length and matches the given regex. """ if isinstance(regex, int): length = regex @@ -31,13 +31,23 @@ def matches(regex: str | int, /, length: int = 0, *, flags=0): return (not length or len(s) < length) and bool(re.fullmatch(regex, s, flags=flags)) return validator + def must_be(obj: _T | Any, typ: type[_T] | Iterable[type], message: str, *, exc = TypeError) -> _T: """ Raise TypeError if the requested object is not of the desired type(s), with a nice message. + + (Not properly a validator.) """ if not isinstance(obj, typ): raise TypeError(f'{message}, not {obj.__class__.__name__!r}') return obj -__all__ = ('matches', ) \ No newline at end of file +def not_greater_than(y): + """ + Return a function that returns True if X is not greater than (i.e. lesser than or equal to) the given value. + """ + return lambda x: x <= y + +__all__ = ('matches', 'not_greater_than') +