Compare commits
12 Commits
2f4d505f0c
...
8c2f01fdae
Author | SHA1 | Date |
---|---|---|
Felix Stupp | 8c2f01fdae | 11 months ago |
Felix Stupp | 61a6257534 | 11 months ago |
Felix Stupp | f8d80ed209 | 11 months ago |
Felix Stupp | cdea170178 | 11 months ago |
Felix Stupp | 90600abf4a | 11 months ago |
Felix Stupp | 5b4dd2e18b | 11 months ago |
Felix Stupp | ab4a5eaec8 | 11 months ago |
Felix Stupp | ae8f8af954 | 11 months ago |
Felix Stupp | 1de9c82d4a | 11 months ago |
Felix Stupp | f91f15e81f | 11 months ago |
Felix Stupp | 0bdc11d5b2 | 11 months ago |
Felix Stupp | 38772d64e2 | 11 months ago |
@ -1,161 +0,0 @@
|
||||
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 all_same(iterable: Iterable) -> bool:
|
||||
it = iter(iterable)
|
||||
first = next(it)
|
||||
return all(first == elem for elem in it)
|
||||
|
||||
|
||||
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)
|
@ -0,0 +1,36 @@
|
||||
from ._converter import (
|
||||
date_to_datetime,
|
||||
to_just_number,
|
||||
)
|
||||
from ._itertools import (
|
||||
all_same,
|
||||
fix_iter,
|
||||
iter_lookahead,
|
||||
limit_iter,
|
||||
)
|
||||
from ._search import (
|
||||
search_source_by_keys,
|
||||
)
|
||||
from ._setting_handler import (
|
||||
update_bool_value,
|
||||
)
|
||||
from ._string import (
|
||||
trim,
|
||||
)
|
||||
from ._subprocess import (
|
||||
call,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"all_same",
|
||||
"call",
|
||||
"date_to_datetime",
|
||||
"fix_iter",
|
||||
"iter_lookahead",
|
||||
"limit_iter",
|
||||
"search_source_by_keys",
|
||||
"to_just_number",
|
||||
"trim",
|
||||
"update_bool_value",
|
||||
]
|
@ -0,0 +1,15 @@
|
||||
from datetime import date, datetime
|
||||
from typing import (
|
||||
Optional,
|
||||
)
|
||||
|
||||
|
||||
def date_to_datetime(d: date) -> datetime:
|
||||
return datetime(d.year, d.month, d.day)
|
||||
|
||||
|
||||
def to_just_number(name: str) -> Optional[int]:
|
||||
try:
|
||||
return int(name)
|
||||
except ValueError:
|
||||
return None
|
@ -0,0 +1,190 @@
|
||||
import itertools
|
||||
from typing import (
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Literal,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
overload,
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# check Iterable for same values
|
||||
|
||||
|
||||
def all_same(iterable: Iterable) -> bool:
|
||||
"""
|
||||
Returns True if all items of Iterable are equal, False otherwise.
|
||||
"""
|
||||
it = iter(iterable)
|
||||
first = next(it)
|
||||
return all(first == elem for elem in it)
|
||||
|
||||
|
||||
# fix Iterables
|
||||
|
||||
|
||||
class _IterFixer(Iterator[T]):
|
||||
__it: Iterator[T]
|
||||
|
||||
def __init__(self, it: Iterator[T]) -> None:
|
||||
super().__init__()
|
||||
self.__it = it
|
||||
|
||||
def __iter__(self) -> Iterator[T]:
|
||||
# this method is the reason for this class
|
||||
# some iterators miss it and so are not Iterable without this proxy
|
||||
return self
|
||||
|
||||
def __next__(self) -> T:
|
||||
return next(self.__it)
|
||||
|
||||
|
||||
def fix_iter(iterable: Iterable[T]) -> Iterable[T]:
|
||||
"""
|
||||
wraps the iterator of iterable
|
||||
into a simple proxy Iterator/Iterable implementation.
|
||||
|
||||
Required in weird edgecases,
|
||||
where the iterator of an iterable is not an iterable.
|
||||
"""
|
||||
return _IterFixer(iter(iterable))
|
||||
|
||||
|
||||
# lookahead on Iterables
|
||||
|
||||
|
||||
@overload
|
||||
def iter_lookahead(
|
||||
iterable: Iterable[T],
|
||||
get_first: Literal[False] = False,
|
||||
get_last: Literal[False] = False,
|
||||
) -> Iterable[Tuple[T, T]]:
|
||||
"""
|
||||
allows iterating over <iterable> while also getting the last/next item
|
||||
|
||||
The pairs are given as a Tuple and so can be split:
|
||||
```python
|
||||
for last, cur in iter_lookahead(iterable): ...
|
||||
```
|
||||
|
||||
The first and last tuple will contain the first / last two entries, so that
|
||||
- `iterable[0]` only appears in `last` and not in `cur`
|
||||
- `iterable[-1]` only appears in `cur` and not in `last`
|
||||
|
||||
You can change that behavior with <get_first>/<get_last>.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def iter_lookahead(
|
||||
iterable: Iterable[T],
|
||||
get_first: Literal[True],
|
||||
get_last: Literal[False] = False,
|
||||
) -> Iterable[Tuple[None, T] | Tuple[T, T]]:
|
||||
"""
|
||||
allows iterating over <iterable> while also getting the last/next item
|
||||
|
||||
The pairs are given as a Tuple and so can be split:
|
||||
```python
|
||||
for last, cur in iter_lookahead(iterable): ...
|
||||
```
|
||||
|
||||
- The first tuple will look like `(None, cur)`,
|
||||
so that `iterable[0]` will appear in both `cur` and `last`.
|
||||
- The last tuple will contain the last two entries,
|
||||
so that `iterable[-1]` only appears in `cur` and not in `last`.
|
||||
|
||||
You can change that behavior with <get_first>/<get_last>.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@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]]:
|
||||
"""
|
||||
allows iterating over <iterable> while also getting the last/next item
|
||||
|
||||
The pairs are given as a Tuple and so can be split:
|
||||
```python
|
||||
for last, cur in iter_lookahead(iterable): ...
|
||||
```
|
||||
|
||||
- The first tuple will contain the first two entries,
|
||||
so that `iterable[0]` only appears in `cur` and not in `last`.
|
||||
- The last tuple will look like `(last, None)`,
|
||||
so that `iterable[-1]` will appear in both `cur` and `last`.
|
||||
|
||||
You can change that behavior with <get_first>/<get_last>.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
@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]]:
|
||||
"""
|
||||
allows iterating over <iterable> while also getting the last/next item
|
||||
|
||||
The pairs are given as a Tuple and so can be split:
|
||||
```python
|
||||
for last, cur in iter_lookahead(iterable): ...
|
||||
```
|
||||
|
||||
The first / last tuple will look like `(None, T)` / `(T, None)`,
|
||||
so that both `iterable[0]` and `iterable[-1]`
|
||||
will both appear in `cur` and `last`.
|
||||
|
||||
You can change that behavior with <get_first>/<get_last>.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
def iter_lookahead(
|
||||
iterable: Iterable[T],
|
||||
get_first: bool = False,
|
||||
get_last: bool = False,
|
||||
) -> Iterable[Tuple[None, T] | Tuple[T, T] | Tuple[T, None]]:
|
||||
"""
|
||||
allows iterating over <iterable> while also getting the last/next item
|
||||
|
||||
The pairs are given as a Tuple and so can be split:
|
||||
```python
|
||||
for last, cur in iter_lookahead(iterable): ...
|
||||
```
|
||||
|
||||
The behavior for the border cases can be configured.
|
||||
If <get_first> is True, the first tuple will be `(None, last)`.
|
||||
If <get_last> is True, the last tuple will be `(cur, None)`.
|
||||
Otherwise, both values will be set at the borders respectively.
|
||||
See the docstrings for the overloaded methods.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
# limit Iterables
|
||||
|
||||
|
||||
def limit_iter(iter: Iterable[T], limit: int) -> List[T]:
|
||||
"""gets the first <limit> elements of <iter> and puts them in a list"""
|
||||
return list(itertools.islice(iter, limit))
|
@ -0,0 +1,24 @@
|
||||
from typing import (
|
||||
Callable,
|
||||
Optional,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
K = TypeVar("K")
|
||||
|
||||
|
||||
def search_source_by_keys(
|
||||
source: Callable[[K], Optional[T]],
|
||||
all_keys: Sequence[K],
|
||||
) -> Optional[T]:
|
||||
"""
|
||||
tries <all_keys> in <source> and returns first result `!= None`, otherwise `None`
|
||||
"""
|
||||
for key in all_keys:
|
||||
item = source(key)
|
||||
if item is not None:
|
||||
return item
|
||||
return None
|
@ -0,0 +1,16 @@
|
||||
from typing import (
|
||||
Literal,
|
||||
)
|
||||
|
||||
|
||||
def update_bool_value(
|
||||
old_value: bool,
|
||||
new_value: 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
|
@ -0,0 +1,31 @@
|
||||
import sys
|
||||
|
||||
|
||||
# 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)
|
@ -0,0 +1,21 @@
|
||||
import subprocess
|
||||
from typing import (
|
||||
IO,
|
||||
Optional,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
|
||||
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
|
Loading…
Reference in New Issue