# coding: utf-8
import argparse
import datetime
import re
import subprocess
from numbers import Number
from typing import Any, Dict, List, Optional, Tuple, Union
from ._colorings import toBLUE, toGREEN, toRED
from ._exceptions import KeyError
NoneType = type(None)
[docs]def handleKeyError(lst: List[Any], **kwargs):
"""Check whether all ``kwargs.values()`` in the ``lst``.
Args:
lst (List[Any]) : candidates.
kwargs (dict) : ``key`` is the varname that is easy to understand when an error occurs
Examples:
>>> from veditor.utils import handleKeyError
>>> handleKeyError(lst=range(3), val=1)
>>> handleKeyError(lst=range(3), val=100)
KeyError: Please choose the argment val from ['0', '1', '2']. you chose 100
>>> handleKeyError(lst=range(3), val1=1, val2=2)
>>> handleKeyError(lst=range(3), val1=1, val2=100)
KeyError: Please choose the argment val2 from ['0', '1', '2']. you chose 100
Raise:
KeyError: If ``kwargs.values()`` not in the ``lst``
"""
for k, v in kwargs.items():
if v not in lst:
lst = ", ".join([f"'{toGREEN(e)}'" for e in lst])
raise KeyError(
f"Please choose the argment {toBLUE(k)} from [{lst}]. you chose {toRED(v)}"
)
[docs]def class2str(class_: object) -> str:
"""Convert class to str.
Args:
class_ (object): class object
Returns:
str : Class name.
Examples:
>>> from veditor.utils import class2str
>>> class2str(str)
'str'
>>> class2str(tuple)
'tuple'
"""
return re.sub(r"<class '(.*?)'>", r"\1", str(class_))
[docs]def handleTypeError(types: List = [Any], **kwargs):
"""Check whether all types of ``kwargs.values()`` match any of ``types``.
Args:
types (List[Any]) : Candidate types.
kwargs (dict) : ``key`` is the varname that is easy to understand when an error occurs
Examples:
>>> from veditor.utils import handleTypeError
>>> handleTypeError(types=[str], val="foo")
>>> handleTypeError(types=[str, int], val=1)
>>> handleTypeError(types=[str, int], val=1.)
TypeError: val must be one of ['str', 'int'], not float
>>> handleTypeError(types=[str], val1="foo", val2="bar")
>>> handleTypeError(types=[str, int], val1="foo", val2=1.)
TypeError: val2 must be one of ['str', 'int'], not float
Raise:
TypeError: If the types of ``kwargs.values()`` are none of the ``types``
"""
types = tuple([NoneType if e is None else e for e in types])
for k, v in kwargs.items():
if not isinstance(v, types):
str_true_types = ", ".join([f"'{toGREEN(class2str(t))}'" for t in types])
srt_false_type = class2str(type(v))
if len(types) == 1:
err_msg = f"must be {str_true_types}"
else:
err_msg = f"must be one of [{str_true_types}]"
raise TypeError(f"{toBLUE(k)} {err_msg}, not {toRED(srt_false_type)}")
[docs]def str_strip(string: str) -> str:
"""Convert all consecutive whitespace characters to `' '` (half-width whitespace), then return a copy of the string with leading and trailing whitespace removed.
Args:
string (str) : string
Returns:
str : A copy of the string with leading and trailing whitespace removed
Example:
>>> from veditor.utils import str_strip
>>> str_strip(" hoge ")
'hoge'
>>> str_strip(" ho ge ")
'ho ge'
>>> str_strip(" ho g e")
'ho g e'
"""
return re.sub(pattern=r"[\s ]+", repl=" ", string=str(string)).strip()
[docs]def now_str(tz=None, fmt="%Y-%m-%d@%H.%M.%S"):
"""Returns new datetime string representing current time local to ``tz`` under the control of an explicit format string.
Args:
tz (datetime.timezone) : Timezone object. If no ``tz`` is specified, uses local timezone.
fmt (str) : format string. See `Python Documentation <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes>`_
Returns:
str : A datetime string.
Example:
>>> from veditor.utils import now_str
>>> now_str()
'2020-09-14@22.31.17'
>>>now_str(fmt="%A, %d. %B %Y %I:%M%p")
Monday, 14. September 2020 10:31PM'
>>> now_str(tz=datetime.timezone.utc)
'2020-09-14@13.31.17'
"""
return datetime.datetime.now(tz=tz).strftime(fmt)
[docs]def readable_bytes(byte: Number) -> Tuple[Number, str]:
"""Unit conversion for readability.
Args:
byte (Number): File byte [B].
Examples:
>>> from veditor.utils import readable_bytes
>>> for i in range(1,30,3):
... byte = pow(10,i)
... size, unit = readable_bytes(pow(10,i))
... print(f"{byte:.1g}[B] = {size:.2f}[{unit}]")
1e+01[B] = 10.00[B]
1e+04[B] = 9.77[KB]
1e+07[B] = 9.54[MB]
1e+10[B] = 9.31[GB]
1e+13[B] = 9.09[TB]
1e+16[B] = 8.88[PB]
1e+19[B] = 8.67[EB]
1e+22[B] = 8.47[ZB]
1e+25[B] = 8.27[YB]
1e+28[B] = 8271.81[YB]
"""
units = ["", "K", "M", "G", "T", "P", "E", "Z", "Y"]
for unit in units:
if (abs(byte) < 1024.0) or (unit == units[-1]):
break
byte /= 1024.0 # size >> 10
return (byte, unit + "B")
[docs]def flatten_dual(lst: List[List[Any]]) -> List[Any]:
"""Flatten double list.
Args:
lst (List[List[Any]]): Dual list.
Returns:
List[Any] : Flattened single list.
Example:
>>> from pycharmers.utils import flatten_dual
>>> flatten_dual([[1,2,3],[4,5,6]])
[1, 2, 3, 4, 5, 6]
>>> flatten_dual([[[1,2,3]],[4,5,6]])
[[1, 2, 3], 4, 5, 6]
>>> flatten_dual(flatten_dual([[[1,2,3]],[4,5,6]]))
TypeError: 'int' object is not iterable
Raise:
TypeError: If list is not a dual list.
"""
return [element for sublist in lst for element in sublist]
_trbl: List[str] = ["top", "right", "bottom", "left"]
[docs]def assign_trbl(
data: Dict[str, Any],
name: str,
default: Union[Number, List[Number]] = 0,
ret_name: bool = False,
) -> Union[
Tuple[Tuple[Number, Number, Number, Number], Tuple[str, str, str, str]],
Tuple[Number, Number, Number, Number],
]:
"""Return the ``name`` 's values of [``Top``, ``Right``, ``Bottom``, ``Left``] from ``data``. Determine the each position as well as css.
Args:
data (Dict[str,Any]) : A dictionary which stores data.
name (str) : The name of the value you want to assign..
default (Union[Number,List[Number]], optional) : Default Value. Defaults to ``0``.
ret_name (bool, optional) : Whether to return names or not. Defaults to ``False``.
Returns:
Union[Tuple[Tuple[Number, Number, Number, Number], Tuple[str,str,str,str]], Tuple[Number, Number, Number, Number]]: Values of ``Top``, ``Right``, ``Bottom``, ``Left``. If ``ret_name`` is ``True``, add names.
Examples:
>>> from veditor.utils import assign_trbl
>>> assign_trbl(data={"margin": [1,2,3,4]}, name="margin")
(1, 2, 3, 4)
>>> assign_trbl(data={"margin": [1,2,3]}, name="margin")
(1, 2, 3, 2)
>>> assign_trbl(data={"margin": [1,2]}, name="margin")
(1, 2, 1, 2)
>>> assign_trbl(data={"margin": 1}, name="margin")
(1, 1, 1, 1)
>>> assign_trbl(data={"margin": 1}, name="padding", default=5)
(5, 5, 5, 5)
"""
vals = data.get(name, default)
if isinstance(vals, (Number, NoneType)):
vals = [vals]
if len(vals) == 0:
t = r = b = l = None
elif len(vals) == 1:
t = r = b = l = vals[0]
elif len(vals) == 2:
t = b = vals[0]
l = r = vals[1]
elif len(vals) == 3:
t, r, b = vals
l = r
elif len(vals) >= 4:
t, r, b, l = vals[:4]
ret: List[Number] = []
names: List[str] = []
for s, v in zip(_trbl, [t, r, b, l]):
_name = f"{name}_{s}"
ret.append(data.get(_name.replace("_", "-"), data.get(_name, v)))
names.append(_name)
if ret_name:
return (tuple(ret), tuple(names))
return tuple(ret)
[docs]def openf(file_path: str, timeout: Optional[int] = None, shell: bool = True) -> None:
"""Open a file in Finder.
Args:
file_path (str) : Path to the file to be opened.
timeout (Optional[int], optional) : [description]. Defaults to ``None``.
shell (bool, optional) : [description]. Defaults to ``True``.
"""
subprocess.call(f"open '{file_path}'", timeout=timeout, shell=shell)