docstring -> typing
This commit is contained in:
@@ -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
@@ -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
@@ -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]))
|
||||||
|
|||||||
Reference in New Issue
Block a user