You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

156 lines
3.6 KiB
Python

from datetime import date, datetime
import itertools
import subprocess
import sys
from typing import (
IO,
Iterable,
Iterator,
List,
Literal,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
overload,
)
def call(
args: Sequence[str],
check: bool = True,
stdin: Optional[IO] = None,
) -> subprocess.CompletedProcess:
proc = subprocess.run(
args,
capture_output=True,
check=check,
text=True,
stdin=stdin,
)
return proc
# source: https://peps.python.org/pep-0257/#handling-docstring-indentation
def trim(docstring: str) -> str:
"""
Trim strings like docstrings are trimmed following PEP 257
"""
if not docstring:
return ""
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxsize
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxsize:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return "\n".join(trimmed)
def update_bool_value(
old_value: bool, new_value: Union[bool, Literal["toggle"]]
) -> bool:
if new_value == "toggle":
return not old_value
if type(new_value) != bool:
raise Exception(
f'Invalid type of new_value: Expected bool or literal "toggle", got type={type(new_value)!r}, value={new_value!r}'
)
return new_value
T = TypeVar("T")
def limit_iter(iter: Iterable[T], limit: int) -> List[T]:
return list(itertools.islice(iter, limit))
class _IterFixer(Iterator[T]):
__it: Iterator[T]
def __init__(self, it: Iterator[T]) -> None:
super().__init__()
self.__it = it
def __iter__(self) -> Iterator[T]:
return self
def __next__(self) -> T:
return next(self.__it)
def fix_iter(iterable: Iterable[T]) -> Iterable[T]:
return _IterFixer(iter(iterable))
@overload
def iter_lookahead(
iterable: Iterable[T],
get_first: Literal[False] = False,
get_last: Literal[False] = False,
) -> Iterable[Tuple[T, T]]:
...
@overload
def iter_lookahead(
iterable: Iterable[T],
get_first: Literal[True],
get_last: Literal[False] = False,
) -> Iterable[Tuple[None, T] | Tuple[T, T]]:
...
@overload
def iter_lookahead(
iterable: Iterable[T],
get_first: Literal[False] = False,
get_last: Literal[True] = True, # <- default only to satisfy python
) -> Iterable[Tuple[T, T] | Tuple[T, None]]:
...
@overload
def iter_lookahead(
iterable: Iterable[T],
get_first: Literal[True],
get_last: Literal[True],
) -> Iterable[Tuple[None, T] | Tuple[T, T] | Tuple[T, None]]:
...
def iter_lookahead(
iterable: Iterable[T],
get_first: bool = False,
get_last: bool = False,
) -> Iterable[Tuple[None, T] | Tuple[T, T] | Tuple[T, None]]:
it = iter(iterable)
last = next(it)
if get_first:
yield None, last
for cur in it:
yield last, cur
last = cur
if get_last:
yield last, None
def date_to_datetime(d: date) -> datetime:
return datetime(d.year, d.month, d.day)