2019-10-08 09:39:27 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""Global utility methods and classes."""
|
|
|
|
|
2019-10-07 06:52:00 +00:00
|
|
|
import os
|
|
|
|
import sys
|
2023-02-08 09:21:23 +00:00
|
|
|
from collections.abc import Iterable
|
2019-10-07 06:52:00 +00:00
|
|
|
|
2024-06-17 11:51:03 +00:00
|
|
|
import structlog
|
2019-10-07 12:44:45 +00:00
|
|
|
|
|
|
|
|
2023-10-16 10:10:57 +00:00
|
|
|
def strtobool(value):
|
|
|
|
"""Convert a string representation of truth to true or false."""
|
|
|
|
|
|
|
|
_map = {
|
|
|
|
"y": True,
|
|
|
|
"yes": True,
|
|
|
|
"t": True,
|
|
|
|
"true": True,
|
|
|
|
"on": True,
|
|
|
|
"1": True,
|
|
|
|
"n": False,
|
|
|
|
"no": False,
|
|
|
|
"f": False,
|
|
|
|
"false": False,
|
|
|
|
"off": False,
|
2023-11-10 13:50:50 +00:00
|
|
|
"0": False,
|
2023-10-16 10:10:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
return _map[str(value).lower()]
|
|
|
|
except KeyError as err:
|
|
|
|
raise ValueError(f'"{value}" is not a valid bool value') from err
|
|
|
|
|
|
|
|
|
2019-10-08 13:55:24 +00:00
|
|
|
def to_bool(string):
|
|
|
|
return bool(strtobool(str(string)))
|
|
|
|
|
|
|
|
|
2022-02-26 12:35:34 +00:00
|
|
|
def flatten(items):
|
|
|
|
for x in items:
|
|
|
|
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
|
2023-08-29 08:19:52 +00:00
|
|
|
yield from flatten(x)
|
2022-02-26 12:35:34 +00:00
|
|
|
else:
|
|
|
|
yield x
|
|
|
|
|
|
|
|
|
2023-01-22 14:02:09 +00:00
|
|
|
def _split_string(string, delimiter, escape, maxsplit=None):
|
|
|
|
result = []
|
|
|
|
current_element = []
|
|
|
|
iterator = iter(string)
|
|
|
|
count_split = 0
|
|
|
|
skip_split = False
|
|
|
|
|
|
|
|
for character in iterator:
|
|
|
|
if maxsplit and count_split >= maxsplit:
|
|
|
|
skip_split = True
|
|
|
|
|
|
|
|
if character == escape and not skip_split:
|
|
|
|
try:
|
|
|
|
next_character = next(iterator)
|
|
|
|
if next_character != delimiter and next_character != escape:
|
|
|
|
# Do not copy the escape character if it is intended to escape either the
|
|
|
|
# delimiter or the escape character itself. Copy the escape character
|
|
|
|
# if it is not used to escape either of these characters.
|
|
|
|
current_element.append(escape)
|
|
|
|
current_element.append(next_character)
|
|
|
|
count_split += 1
|
|
|
|
except StopIteration:
|
|
|
|
current_element.append(escape)
|
|
|
|
elif character == delimiter and not skip_split:
|
|
|
|
result.append("".join(current_element))
|
|
|
|
current_element = []
|
|
|
|
count_split += 1
|
|
|
|
else:
|
|
|
|
current_element.append(character)
|
|
|
|
|
|
|
|
result.append("".join(current_element))
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2024-06-17 11:51:03 +00:00
|
|
|
def sysexit(code=1):
|
|
|
|
sys.exit(code)
|
|
|
|
|
|
|
|
|
|
|
|
def sysexit_with_message(msg, code=1, **kwargs):
|
|
|
|
structlog.get_logger().critical(str(msg).strip(), **kwargs)
|
|
|
|
sysexit(code)
|
2019-10-07 06:52:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Singleton(type):
|
2020-04-05 21:16:53 +00:00
|
|
|
"""Meta singleton class."""
|
|
|
|
|
2019-10-07 06:52:00 +00:00
|
|
|
_instances = {}
|
|
|
|
|
|
|
|
def __call__(cls, *args, **kwargs):
|
|
|
|
if cls not in cls._instances:
|
2023-01-20 10:56:12 +00:00
|
|
|
cls._instances[cls] = super().__call__(*args, **kwargs)
|
2019-10-07 06:52:00 +00:00
|
|
|
return cls._instances[cls]
|
|
|
|
|
|
|
|
|
|
|
|
class FileUtils:
|
2020-04-05 21:16:53 +00:00
|
|
|
"""Mics static methods for file handling."""
|
|
|
|
|
2019-10-07 06:52:00 +00:00
|
|
|
@staticmethod
|
|
|
|
def create_path(path):
|
|
|
|
os.makedirs(path, exist_ok=True)
|
|
|
|
|
|
|
|
@staticmethod
|
2019-10-08 09:30:31 +00:00
|
|
|
def query_yes_no(question, default=True):
|
2023-01-20 10:56:12 +00:00
|
|
|
"""
|
|
|
|
Ask a yes/no question via input() and return their answer.
|
2019-10-07 06:52:00 +00:00
|
|
|
|
|
|
|
"question" is a string that is presented to the user.
|
|
|
|
"default" is the presumed answer if the user just hits <Enter>.
|
|
|
|
It must be "yes" (the default), "no" or None (meaning
|
|
|
|
an answer is required of the user).
|
|
|
|
|
|
|
|
The "answer" return value is one of "yes" or "no".
|
|
|
|
"""
|
2023-01-20 10:56:12 +00:00
|
|
|
prompt = "[Y/n]" if default else "[N/y]"
|
2019-10-08 09:30:31 +00:00
|
|
|
|
2024-06-12 18:59:55 +00:00
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
# input method is safe in python3
|
|
|
|
choice = input(f"{question} {prompt} ") or default # nosec
|
|
|
|
return to_bool(choice)
|
|
|
|
except ValueError:
|
|
|
|
print("Invalid input. Please enter 'y' or 'n'.") # noqa: T201
|
|
|
|
except KeyboardInterrupt as e:
|
|
|
|
raise e
|