Files
meme-otron/meme_otron/utils.py
T
2020-04-28 17:07:22 +02:00

208 lines
5.5 KiB
Python

import re
import sys
import os.path as path
from typing import List, Optional, Union, Tuple
from Levenshtein import distance
# region path utils
def relative_path(file: str, *args: str) -> str:
return path.realpath(path.join(path.dirname(path.realpath(file)), *args))
# endregion
# region dict utils
def read_key_safe(d: dict, k: str, default=None, *,
types: Optional[List[type]] = None,
is_list: bool = False,
is_list_size: Optional[int] = None):
try:
return read_key(d, k, default, types=types, is_list=is_list, is_list_size=is_list_size)
except KeyError:
return default
def read_key(d: dict, k: str, default=None, *,
types: Optional[List[type]] = None,
is_list: bool = False,
is_list_size: Optional[int] = None):
if k in d:
v = d[k]
if types is not None:
try:
check_type(v, types, is_list, is_list_size)
except TypeError as e:
raise TypeError(f"'{k}' is {e}")
return v
elif default is not None:
return default
else:
raise KeyError(k)
# endregion
# region type utils
def check_type(obj, types: List[type], is_list: bool = False, is_list_size: Optional[int] = None):
if is_list:
if not is_list_of(obj, types, is_list_size):
if is_list_size is not None:
raise TypeError(f"not a list of {is_list_size} {types[0].__name__}")
else:
raise TypeError(f"not a list of {types[0].__name__}")
else:
if not is_list_of([obj], types):
raise TypeError(f"not a {types[0].__name__}")
def is_list_of(obj, types: List[type], length: Optional[int] = None) -> bool:
if not (isinstance(obj, list)):
return False
for item in obj:
found = False
for t in types:
if isinstance(item, t):
found = True
break
if not found:
return False
if length is not None and len(obj) != length:
return False
return True
# endregion
# region args utils
args_regex = re.compile('"([^"]*)"|\'([^\']*)\'|([^ ]+)')
def parse_arguments(src: str) -> List[str]:
def get_found_match(m: list) -> str:
f = [g for g in m if len(g) > 0]
if len(f) > 0:
return f[0]
return ""
return [get_found_match(m) for m in args_regex.findall(src)]
def read_argument(args: List[str], *names: str, valued: bool = False, delete: bool = False):
for i, arg in enumerate(args):
if arg.lower() in names:
if delete:
del args[i]
i -= 1
if not valued:
return True
else:
v = None
if i + 1 < len(args):
v = args[i + 1]
if delete:
del args[i + 1]
return v
return None
def split_arguments(args: Union[List[str], Tuple[str]], separator: str) -> List[List[str]]:
output = [[]]
for argument in args:
if argument == separator:
output += [[]]
else:
output[-1] += [argument]
return [part for part in output if len(part) > 0]
# endregion
# region lang utils
def find_nearest(word: str, wlist: List[str], threshold: int = 5) -> Optional[str]:
distances = [
(distance(word, w), # distance
abs(len(w) - len(word)), # length diff
w)
for w in wlist]
distances.sort(key=lambda v: v[1]) # sort by length diff to get the closest (in length) first
found = min(distances, key=lambda v: v[0] - v[1]) # get the closest in lev. distance
if found[0] - found[1] > threshold: # distance is too much
return None
return found[2]
# endregion
# region format utils
def justify_text(src: str, n_lines: int) -> Optional[str]:
spaces_indexes = find_all(src, " ")
if n_lines - 1 > len(spaces_indexes):
return None # impossible
if n_lines - 1 == len(spaces_indexes):
return replace_at(src, "\n", spaces_indexes, 1)
breaks_positions = [k * (len(src) - 1) / n_lines for k in range(1, n_lines)]
break_indexes = place_line_breaks(breaks_positions, spaces_indexes)
return replace_at(src, "\n", break_indexes, 1)
def place_line_breaks(breaks_positions: List[float], spaces_indexes: List[int]) -> List[int]:
breaks_positions = breaks_positions[:]
breaks_indexes = []
dist = sys.maxsize
for i, value in enumerate(spaces_indexes):
if not len(breaks_positions):
break
if dist < abs(value - breaks_positions[0]):
breaks_indexes += [spaces_indexes[i - 1]]
breaks_positions.pop(0)
else:
dist = abs(value - breaks_positions[0])
if len(breaks_positions):
breaks_indexes += [spaces_indexes[-1]]
return breaks_indexes
# endregion
# region string utils
def find_all(src: str, pattern: str) -> List[int]:
indexes = []
i = safe_index(src, pattern)
while i is not None:
indexes += [i]
i = safe_index(src, pattern, i + 1)
return indexes
def replace_at(src: str, pattern: str, indexes: List[int], remove: int) -> str:
output = ""
start_index = 0
for i in indexes:
output += src[start_index:i] + pattern
start_index = i + remove
output += src[start_index:]
return output
def safe_index(src: Union[str, list], pattern, start: int = 0):
try:
return src.index(pattern, start)
except ValueError:
return None
# endregion