docstring -> typing

This commit is contained in:
klemek
2020-04-27 14:32:54 +02:00
parent de22001504
commit f7ac7bc5ee
3 changed files with 59 additions and 147 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ def fit_text(size, text):
k = 0 # number of lines k = 0 # number of lines
while k == 0 or (t is not None and text_size[0] >= max_width): while k == 0 or (t is not None and text_size[0] >= max_width):
k += 1 k += 1
t = utils.break_text(text.text, k) t = utils.justify_text(text.text, k)
if t is not None: if t is not None:
text_size = font.getsize_multiline(t, stroke_width=text.stroke_width * font_size) text_size = font.getsize_multiline(t, stroke_width=text.stroke_width * font_size)
if t is None: if t is None:
+49 -137
View File
@@ -1,56 +1,28 @@
import re import re
import sys import sys
import os.path as path import os.path as path
from typing import List, Optional, Union
from Levenshtein import distance from Levenshtein import distance
def relative_path(file, *args): def relative_path(file: str, *args: str) -> str:
"""
Get the full path from a starting file and a relative path
:param (str) file:
:param (str) args:
:rtype str
:return:
"""
return path.realpath(path.join(path.dirname(path.realpath(file)), *args)) return path.realpath(path.join(path.dirname(path.realpath(file)), *args))
def read_key_safe(d, k, default=None, *, types=None, is_list=False, is_list_size=None): def read_key_safe(d: dict, k: str, default=None, *,
""" types: Optional[List[type]] = None,
Read a value from a dict or return the default value if not found. is_list: bool = False,
Can also check the type of the value. is_list_size: Optional[int] = None):
:param (dict) d: source dict
:param (str) k: key to read
:param default: default value
:param (list of type|None) types: types to check
:param (bool) is_list: if the type is a list of types
:param (int|None) is_list_size: size of the list to enforce or None
:raises TypeError:
:return:
"""
try: try:
return read_key(d, k, default, types=types, is_list=is_list, is_list_size=is_list_size) return read_key(d, k, default, types=types, is_list=is_list, is_list_size=is_list_size)
except KeyError: except KeyError:
return default return default
def read_key(d, k, default=None, *, types=None, is_list=False, is_list_size=None): def read_key(d: dict, k: str, default=None, *,
""" types: Optional[List[type]] = None,
Read a value from a dict or return the default value or throw an error if the default is None. is_list: bool = False,
Can also check the type of the value. is_list_size: Optional[int] = None):
:param (dict) d: source dict
:param (str) k: key to read
:param default: default value
:param (list of type|None) types: types to check
:param (bool) is_list: if the type is a list of types
:param (int|None) is_list_size: size of the list to enforce or None
:raises TypeError:
:raises KeyError:
:return:
"""
if k in d: if k in d:
v = d[k] v = d[k]
if types is not None: if types is not None:
@@ -65,18 +37,7 @@ def read_key(d, k, default=None, *, types=None, is_list=False, is_list_size=None
raise KeyError(k) raise KeyError(k)
def check_type(obj, types, is_list=False, is_list_size=None): def check_type(obj, types: List[type], is_list: bool = False, is_list_size: Optional[int] = None):
"""
Check the type from a list of possibilities.
Can check the types of all elements of a list.
:param obj:
:param (list of type|None) types: types to check
:param (bool) is_list: if the type is a list of types
:param (int|None) is_list_size: size of the list to enforce or None
:raises TypeError:
:return:
"""
if is_list: if is_list:
if not is_list_of(obj, types, is_list_size): if not is_list_of(obj, types, is_list_size):
if is_list_size is not None: if is_list_size is not None:
@@ -88,16 +49,7 @@ def check_type(obj, types, is_list=False, is_list_size=None):
raise TypeError(f"not a {types[0].__name__}") raise TypeError(f"not a {types[0].__name__}")
def is_list_of(obj, types, length=None): def is_list_of(obj, types: List[type], length: Optional[int] = None) -> bool:
"""
Check the types of all elements of a list.
:param obj:
:param (list of type) types:
:param (int) length:
:rtype: bool
:return:
"""
if not (isinstance(obj, list)): if not (isinstance(obj, list)):
return False return False
for item in obj: for item in obj:
@@ -116,11 +68,11 @@ def is_list_of(obj, types, length=None):
args_regex = re.compile('"([^"]*)"|\'([^\']*)\'|([^ ]+)') args_regex = re.compile('"([^"]*)"|\'([^\']*)\'|([^ ]+)')
def parse_arguments(s): def parse_arguments(src: str) -> List[str]:
""" """
Split a string into separates arguments Split a string into separates arguments
:param (str) s: :param (str) src:
:rtype: list of str :rtype: list of str
:return: :return:
""" """
@@ -131,104 +83,64 @@ def parse_arguments(s):
return f[0] return f[0]
return "" return ""
return [get_found_match(m) for m in args_regex.findall(s)] return [get_found_match(m) for m in args_regex.findall(src)]
def find_nearest(word, wlist, threshold=5): def find_nearest(word: str, wlist: List[str], threshold: int = 5) -> Optional[str]:
"""
Find the nearest word in a list
:param (str) word:
:param (list of str) wlist:
:param (int) threshold:
:rtype: str | None
:return:
"""
found = min([(distance(word, w) - abs(len(w) - len(word)), w) for w in wlist], key=lambda v: v[0]) found = min([(distance(word, w) - abs(len(w) - len(word)), w) for w in wlist], key=lambda v: v[0])
if found[0] > threshold: if found[0] > threshold:
return None return None
return found[1] return found[1]
def break_text(src, n): def justify_text(src: str, n_lines: int) -> Optional[str]:
""" spaces_indexes = find_all(src, " ")
:param (str) src: source string if n_lines - 1 > len(spaces_indexes):
:param (int) n: number of lines return None # impossible
:rtype: str if n_lines - 1 == len(spaces_indexes):
""" return replace_at(src, "\n", spaces_indexes, 1)
spaces = find_all(src, " ") breaks_positions = [k * (len(src) - 1) / n_lines for k in range(1, n_lines)]
if n - 1 > len(spaces): break_indexes = place_line_breaks(breaks_positions, spaces_indexes)
return None return replace_at(src, "\n", break_indexes, 1)
if n - 1 == len(spaces):
return replace_at(src, "\n", spaces, 1)
ideal = [k * (len(src) - 1) / n for k in range(1, n)]
indexes = best_fit(ideal, spaces)
return replace_at(src, "\n", indexes, 1)
def find_all(src, pattern): def find_all(src: str, pattern: str) -> List[int]:
""" indexes = []
:param (str) src: source string
:param (str) pattern: pattern to find
:rtype: list of int
:return: all indexes of the pattern
"""
o = []
i = safe_index(src, pattern) i = safe_index(src, pattern)
while i is not None: while i is not None:
o += [i] indexes += [i]
i = safe_index(src, pattern, i + 1) i = safe_index(src, pattern, i + 1)
return o return indexes
def replace_at(src, pattern, indexes, remove): def replace_at(src: str, pattern: str, indexes: List[int], remove: int) -> str:
""" output = ""
:param (str) src: source string start_index = 0
:param (str) pattern: string to inject
:param (list of int) indexes: places to inject
:param (int) remove: how much to remove at each index
:rtype: str
:return
"""
o = ""
last = 0
for i in indexes: for i in indexes:
o += src[last:i] + pattern output += src[start_index:i] + pattern
last = i + remove start_index = i + remove
o += src[last:] output += src[start_index:]
return o return output
def best_fit(a, b): def place_line_breaks(breaks_positions: List[float], spaces_indexes: List[int]) -> List[int]:
""" breaks_positions = breaks_positions[:]
select for each item of a the closest item of b breaks_indexes = []
:param (list of float) a:
:param (list of int) b:
:rtype: list of int
"""
a = a[:]
o = []
dist = sys.maxsize dist = sys.maxsize
for i, value in enumerate(b): for i, value in enumerate(spaces_indexes):
if not len(a): if not len(breaks_positions):
break break
if dist < abs(value - a[0]): if dist < abs(value - breaks_positions[0]):
o += [b[i - 1]] breaks_indexes += [spaces_indexes[i - 1]]
a.pop(0) breaks_positions.pop(0)
else: else:
dist = abs(value - a[0]) dist = abs(value - breaks_positions[0])
if len(a): if len(breaks_positions):
o += [b[-1]] breaks_indexes += [spaces_indexes[-1]]
return o return breaks_indexes
def safe_index(src, pattern, start=0): def safe_index(src: Union[str, list], pattern, start: int = 0):
"""
:param (list|str) src:
:param pattern:
:param (int) start:
"""
try: try:
return src.index(pattern, start) return src.index(pattern, start)
except ValueError: except ValueError:
+9 -9
View File
@@ -117,14 +117,14 @@ class Test(TestCase):
self.assertEqual("a nice_plac_", utils.replace_at("a nice place", "_", [6, 11], 1)) self.assertEqual("a nice_plac_", utils.replace_at("a nice place", "_", [6, 11], 1))
def test_break_text(self): def test_break_text(self):
self.assertIsNone(utils.break_text("abcd", 2)) self.assertIsNone(utils.justify_text("abcd", 2))
self.assertIsNone(utils.break_text("abcd efgh", 3)) self.assertIsNone(utils.justify_text("abcd efgh", 3))
self.assertEqual("abcd", utils.break_text("abcd", 1)) self.assertEqual("abcd", utils.justify_text("abcd", 1))
self.assertEqual("abcd\nefgh", utils.break_text("abcd efgh", 2)) self.assertEqual("abcd\nefgh", utils.justify_text("abcd efgh", 2))
self.assertEqual("ab cd\nef gh", utils.break_text("ab cd ef gh", 2)) self.assertEqual("ab cd\nef gh", utils.justify_text("ab cd ef gh", 2))
self.assertEqual("ab\ncd ef\ngh", utils.break_text("ab cd ef gh", 3)) self.assertEqual("ab\ncd ef\ngh", utils.justify_text("ab cd ef gh", 3))
def test_best_fit(self): def test_best_fit(self):
self.assertEqual([5, 9, 15], utils.best_fit([5.2, 14.3, 15.2], [3, 5, 9, 15, 18])) self.assertEqual([5, 9, 15], utils.place_line_breaks([5.2, 14.3, 15.2], [3, 5, 9, 15, 18]))
self.assertEqual([5, 9, 15, 18], utils.best_fit([5.2, 14.3, 14.5, 15.2], [3, 5, 9, 15, 18])) self.assertEqual([5, 9, 15, 18], utils.place_line_breaks([5.2, 14.3, 14.5, 15.2], [3, 5, 9, 15, 18]))
self.assertEqual([5, 9, 15, 18], utils.best_fit([5.2, 14.3, 14.5, 15.2], [3, 5, 9, 15, 18, 20])) self.assertEqual([5, 9, 15, 18], utils.place_line_breaks([5.2, 14.3, 14.5, 15.2], [3, 5, 9, 15, 18, 20]))