| | 1 | 1 | | import codecs |
| | 1 | 2 | | import platform |
| | 1 | 3 | | import inspect |
| | 1 | 4 | | import ctypes |
| | 1 | 5 | | import hashlib |
| | 1 | 6 | | import re |
| | 1 | 7 | | import os |
| | 1 | 8 | | import subprocess |
| | 1 | 9 | | import shutil |
| | 1 | 10 | | import urllib |
| | 1 | 11 | | import stat |
| | 1 | 12 | | import secrets |
| | 1 | 13 | | import string as strin |
| | 1 | 14 | | import sys |
| | 1 | 15 | | import traceback |
| | 1 | 16 | | import warnings |
| | 1 | 17 | | import functools |
| | 1 | 18 | | from datetime import datetime, timedelta, date |
| | 1 | 19 | | from os import listdir |
| | 1 | 20 | | from os.path import isfile, join, isdir |
| | 1 | 21 | | from pathlib import Path |
| | 1 | 22 | | from shutil import copyfile |
| | 1 | 23 | | import typing |
| | 1 | 24 | | from defusedxml.minidom import parse |
| | 1 | 25 | | from OpenSSL import crypto |
| | | 26 | | |
| | | 27 | | |
| | 1 | 28 | | class GeneralUtilities: |
| | | 29 | | |
| | 1 | 30 | | __datetime_format: str = "%Y-%m-%dT%H:%M:%S" |
| | 1 | 31 | | __date_format: str = "%Y-%m-%d" |
| | | 32 | | |
| | 1 | 33 | | @staticmethod |
| | 1 | 34 | | def get_modest_dark_url() -> str: |
| | 0 | 35 | | return "https://aniondev.github.io/CDN/ScriptCollectionDesigns/ModestDark/Style.css" |
| | | 36 | | |
| | 1 | 37 | | @staticmethod |
| | 1 | 38 | | def is_generic(t: typing.Type): |
| | 1 | 39 | | return hasattr(t, "__origin__") |
| | | 40 | | |
| | 1 | 41 | | @staticmethod |
| | 1 | 42 | | def check_arguments(function): |
| | 1 | 43 | | def __check_function(*args, **named_args): |
| | 1 | 44 | | parameters: list = inspect.getfullargspec(function)[0].copy() |
| | 1 | 45 | | arguments: list = list(tuple(args)).copy() |
| | 1 | 46 | | if "self" in parameters: |
| | 1 | 47 | | parameters.remove("self") |
| | 1 | 48 | | arguments.pop(0) |
| | 1 | 49 | | for index, argument in enumerate(arguments): |
| | 1 | 50 | | if argument is not None: # Check type of None is not possible. None is always a valid argument-value |
| | 1 | 51 | | if parameters[index] in function.__annotations__: # Check if a type-hint for parameter exist. If no |
| | | 52 | | # Check type of arguments if the type is a generic type seems to be impossible. |
| | 1 | 53 | | if not GeneralUtilities.is_generic(function.__annotations__[parameters[index]]): |
| | 1 | 54 | | if not isinstance(argument, function.__annotations__[parameters[index]]): |
| | 0 | 55 | | raise TypeError(f"Argument with index {index} for function {function.__name__} ('{str(ar |
| | 1 | 56 | | for index, named_argument in enumerate(named_args): |
| | 1 | 57 | | if named_args[named_argument] is not None: |
| | 1 | 58 | | if parameters[index] in function.__annotations__: |
| | 1 | 59 | | if not GeneralUtilities.is_generic(function.__annotations__.get(named_argument)): |
| | 1 | 60 | | if not isinstance(named_args[named_argument], function.__annotations__.get(named_argument)): |
| | 0 | 61 | | raise TypeError(f"Argument with name {named_argument} for function {function.__name__} ( |
| | 1 | 62 | | return function(*args, **named_args) |
| | 1 | 63 | | __check_function.__doc__ = function.__doc__ |
| | 1 | 64 | | return __check_function |
| | | 65 | | |
| | 1 | 66 | | @staticmethod |
| | 1 | 67 | | def deprecated(func): |
| | 1 | 68 | | @functools.wraps(func) |
| | 1 | 69 | | def new_func(*args, **kwargs): |
| | 0 | 70 | | warnings.simplefilter('always', DeprecationWarning) |
| | 0 | 71 | | warnings.warn(f"Call to deprecated function {func.__name__}", category=DeprecationWarning, stacklevel=2) |
| | 0 | 72 | | warnings.simplefilter('default', DeprecationWarning) |
| | 0 | 73 | | return func(*args, **kwargs) |
| | 1 | 74 | | return new_func |
| | | 75 | | |
| | 1 | 76 | | @staticmethod |
| | 1 | 77 | | @check_arguments |
| | 1 | 78 | | def args_array_surround_with_quotes_if_required(arguments: list[str]) -> list[str]: |
| | 0 | 79 | | result = [] |
| | 0 | 80 | | for argument in arguments: |
| | 0 | 81 | | if " " in argument and not (argument.startswith('"') and argument.endswith('"')): |
| | 0 | 82 | | result.append(f'"{argument}"') |
| | | 83 | | else: |
| | 0 | 84 | | result.append(argument) |
| | 0 | 85 | | return result |
| | | 86 | | |
| | 1 | 87 | | @staticmethod |
| | 1 | 88 | | @check_arguments |
| | 1 | 89 | | def string_to_lines(string: str, add_empty_lines: bool = True, adapt_lines: bool = True) -> list[str]: |
| | 1 | 90 | | result = list() |
| | 1 | 91 | | if (string is not None): |
| | 1 | 92 | | lines = list() |
| | 1 | 93 | | if ("\n" in string): |
| | 1 | 94 | | lines = string.split("\n") |
| | | 95 | | else: |
| | 1 | 96 | | lines.append(string) |
| | 1 | 97 | | for rawline in lines: |
| | 1 | 98 | | if adapt_lines: |
| | 1 | 99 | | line = rawline.replace("\r", "").strip() |
| | | 100 | | else: |
| | 0 | 101 | | line = rawline |
| | 1 | 102 | | if GeneralUtilities.string_is_none_or_whitespace(line): |
| | 1 | 103 | | if add_empty_lines: |
| | 1 | 104 | | result.append(line) |
| | | 105 | | else: |
| | 1 | 106 | | result.append(line) |
| | 1 | 107 | | return result |
| | | 108 | | |
| | 1 | 109 | | @staticmethod |
| | 1 | 110 | | @check_arguments |
| | 1 | 111 | | def string_to_datetime(value: str) -> datetime: |
| | 1 | 112 | | if "." in value: |
| | 1 | 113 | | value = value.split(".")[0] |
| | 1 | 114 | | return datetime.strptime(value, GeneralUtilities.__datetime_format) # value ="2022-10-06T19:26:01" for example |
| | | 115 | | |
| | 1 | 116 | | @staticmethod |
| | 1 | 117 | | @check_arguments |
| | 1 | 118 | | def datetime_to_string(value: datetime) -> str: |
| | 1 | 119 | | value = datetime(year=value.year, month=value.month, day=value.day, hour=value.hour, minute=value.minute, second |
| | 1 | 120 | | return value.strftime(GeneralUtilities.__datetime_format) # returns "2022-10-06T19:26:01" for example |
| | | 121 | | |
| | 1 | 122 | | @staticmethod |
| | 1 | 123 | | @check_arguments |
| | 1 | 124 | | def string_to_date(value: str) -> date: |
| | 1 | 125 | | splitted = value.split("-") |
| | 1 | 126 | | return date(int(splitted[0]), int(splitted[1]), int(splitted[2])) # value ="2022-10-06" for example |
| | | 127 | | |
| | 1 | 128 | | @staticmethod |
| | 1 | 129 | | @check_arguments |
| | 1 | 130 | | def date_to_string(value: date) -> str: |
| | 1 | 131 | | return value.strftime(GeneralUtilities.__date_format) # returns "2022-10-06" for example |
| | | 132 | | |
| | 1 | 133 | | @staticmethod |
| | 1 | 134 | | @check_arguments |
| | 1 | 135 | | def copy_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False) -> None: |
| | 0 | 136 | | GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, |
| | | 137 | | |
| | 1 | 138 | | @staticmethod |
| | 1 | 139 | | @check_arguments |
| | 1 | 140 | | def move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False) -> None: |
| | 0 | 141 | | GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, |
| | | 142 | | |
| | 1 | 143 | | @staticmethod |
| | 1 | 144 | | @check_arguments |
| | 1 | 145 | | def __copy_or_move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files, remove_ |
| | 0 | 146 | | srcDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(source_directory) |
| | 0 | 147 | | dstDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(target_directory) |
| | 0 | 148 | | if (os.path.isdir(source_directory)): |
| | 0 | 149 | | GeneralUtilities.ensure_directory_exists(target_directory) |
| | 0 | 150 | | for file in GeneralUtilities.get_direct_files_of_folder(srcDirFull): |
| | 0 | 151 | | filename = os.path.basename(file) |
| | 0 | 152 | | targetfile = os.path.join(dstDirFull, filename) |
| | 0 | 153 | | if (os.path.isfile(targetfile)): |
| | 0 | 154 | | if overwrite_existing_files: |
| | 0 | 155 | | GeneralUtilities.ensure_file_does_not_exist(targetfile) |
| | | 156 | | else: |
| | 0 | 157 | | raise ValueError(f"Targetfile {targetfile} does already exist") |
| | 0 | 158 | | if remove_source: |
| | 0 | 159 | | shutil.move(file, dstDirFull) |
| | | 160 | | else: |
| | 0 | 161 | | shutil.copy(file, dstDirFull) |
| | 0 | 162 | | for sub_folder in GeneralUtilities.get_direct_folders_of_folder(srcDirFull): |
| | 0 | 163 | | foldername = os.path.basename(sub_folder) |
| | 0 | 164 | | sub_target = os.path.join(dstDirFull, foldername) |
| | 0 | 165 | | GeneralUtilities.__copy_or_move_content_of_folder(sub_folder, sub_target, overwrite_existing_files, remo |
| | 0 | 166 | | if remove_source: |
| | 0 | 167 | | GeneralUtilities.ensure_directory_does_not_exist(sub_folder) |
| | | 168 | | else: |
| | 0 | 169 | | raise ValueError(f"Folder '{source_directory}' does not exist") |
| | | 170 | | |
| | 1 | 171 | | @staticmethod |
| | 1 | 172 | | @check_arguments |
| | 1 | 173 | | def replace_regex_each_line_of_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8", ver |
| | | 174 | | """This function iterates over each line in the file and replaces it by the line which applied regex. |
| | | 175 | | Note: The lines will be taken from open(...).readlines(). So the lines may contain '\\n' or '\\r\\n' for example |
| | 0 | 176 | | if verbose: |
| | 0 | 177 | | GeneralUtilities.write_message_to_stdout(f"Replace '{replace_from_regex}' to '{replace_to_regex}' in '{file} |
| | 0 | 178 | | with open(file, encoding=encoding, mode="r") as f: |
| | 0 | 179 | | lines = f.readlines() |
| | 0 | 180 | | replaced_lines = [] |
| | 0 | 181 | | for line in lines: |
| | 0 | 182 | | replaced_line = re.sub(replace_from_regex, replace_to_regex, line) |
| | 0 | 183 | | replaced_lines.append(replaced_line) |
| | 0 | 184 | | with open(file, encoding=encoding, mode="w") as f: |
| | 0 | 185 | | f.writelines(replaced_lines) |
| | | 186 | | |
| | 1 | 187 | | @staticmethod |
| | 1 | 188 | | @check_arguments |
| | 1 | 189 | | def replace_regex_in_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8") -> None: |
| | 0 | 190 | | with open(file, encoding=encoding, mode="r") as f: |
| | 0 | 191 | | content = f.read() |
| | 0 | 192 | | content = re.sub(replace_from_regex, replace_to_regex, content) |
| | 0 | 193 | | with open(file, encoding=encoding, mode="w") as f: |
| | 0 | 194 | | f.write(content) |
| | | 195 | | |
| | 1 | 196 | | @staticmethod |
| | 1 | 197 | | @check_arguments |
| | 1 | 198 | | def replace_xmltag_in_file(file: str, tag: str, new_value: str, encoding="utf-8") -> None: |
| | 0 | 199 | | GeneralUtilities.replace_regex_in_file(file, f"<{tag}>.*</{tag}>", f"<{tag}>{new_value}</{tag}>", encoding) |
| | | 200 | | |
| | 1 | 201 | | @staticmethod |
| | 1 | 202 | | @check_arguments |
| | 1 | 203 | | def update_version_in_csproj_file(file: str, target_version: str) -> None: |
| | 0 | 204 | | GeneralUtilities.replace_xmltag_in_file(file, "Version", target_version) |
| | 0 | 205 | | GeneralUtilities.replace_xmltag_in_file(file, "AssemblyVersion", target_version + ".0") |
| | 0 | 206 | | GeneralUtilities.replace_xmltag_in_file(file, "FileVersion", target_version + ".0") |
| | | 207 | | |
| | 1 | 208 | | @staticmethod |
| | 1 | 209 | | @check_arguments |
| | 1 | 210 | | def replace_underscores_in_text(text: str, replacements: dict) -> str: |
| | 0 | 211 | | changed = True |
| | 0 | 212 | | while changed: |
| | 0 | 213 | | changed = False |
| | 0 | 214 | | for key, value in replacements.items(): |
| | 0 | 215 | | previousValue = text |
| | 0 | 216 | | text = text.replace(f"__{key}__", value) |
| | 0 | 217 | | if (not text == previousValue): |
| | 0 | 218 | | changed = True |
| | 0 | 219 | | return text |
| | | 220 | | |
| | 1 | 221 | | @staticmethod |
| | 1 | 222 | | @check_arguments |
| | 1 | 223 | | def replace_underscores_in_file(file: str, replacements: dict, encoding: str = "utf-8"): |
| | 0 | 224 | | text = GeneralUtilities.read_text_from_file(file, encoding) |
| | 0 | 225 | | text = GeneralUtilities.replace_underscores_in_text(text, replacements) |
| | 0 | 226 | | GeneralUtilities.write_text_to_file(file, text, encoding) |
| | | 227 | | |
| | 1 | 228 | | @staticmethod |
| | 1 | 229 | | @check_arguments |
| | 1 | 230 | | def reconfigure_standrd_input_and_outputs(): |
| | 0 | 231 | | sys.stdin.reconfigure(encoding='utf-8') |
| | 0 | 232 | | sys.stderr.reconfigure(encoding='utf-8') |
| | 0 | 233 | | sys.stdout.reconfigure(encoding='utf-8') |
| | | 234 | | |
| | 1 | 235 | | @staticmethod |
| | 1 | 236 | | @check_arguments |
| | 1 | 237 | | def write_message_to_stdout(message: str): |
| | 1 | 238 | | for line in GeneralUtilities.string_to_lines(message): |
| | 1 | 239 | | sys.stdout.write(GeneralUtilities.str_none_safe(line)+"\n") |
| | 1 | 240 | | sys.stdout.flush() |
| | | 241 | | |
| | 1 | 242 | | @staticmethod |
| | 1 | 243 | | @check_arguments |
| | 1 | 244 | | def write_message_to_stderr(message: str): |
| | 0 | 245 | | sys.stderr.write(GeneralUtilities.str_none_safe(message)+"\n") |
| | 0 | 246 | | sys.stderr.flush() |
| | | 247 | | |
| | 1 | 248 | | @staticmethod |
| | 1 | 249 | | @check_arguments |
| | 1 | 250 | | def get_advanced_errormessage_for_os_error(os_error: OSError) -> str: |
| | 1 | 251 | | if GeneralUtilities.string_has_content(os_error.filename2): |
| | 0 | 252 | | secondpath = f" {os_error.filename2}" |
| | | 253 | | else: |
| | 1 | 254 | | secondpath = "" |
| | 1 | 255 | | return f"Related path(s): {os_error.filename}{secondpath}" |
| | | 256 | | |
| | 1 | 257 | | @staticmethod |
| | 1 | 258 | | @check_arguments |
| | 1 | 259 | | def write_exception_to_stderr(exception: Exception, extra_message: str = None): |
| | 0 | 260 | | GeneralUtilities.write_exception_to_stderr_with_traceback(exception, None, extra_message) |
| | | 261 | | |
| | 1 | 262 | | @staticmethod |
| | 1 | 263 | | @check_arguments |
| | 1 | 264 | | def write_exception_to_stderr_with_traceback(exception: Exception, current_traceback=None, extra_message: str = None |
| | 0 | 265 | | GeneralUtilities.write_message_to_stderr("Exception(") |
| | 0 | 266 | | GeneralUtilities.write_message_to_stderr("Type: " + str(type(exception))) |
| | 0 | 267 | | GeneralUtilities.write_message_to_stderr("Message: " + str(exception)) |
| | 0 | 268 | | if extra_message is not None: |
| | 0 | 269 | | GeneralUtilities.write_message_to_stderr("Extra-message: " + str(extra_message)) |
| | 0 | 270 | | if isinstance(exception, OSError): |
| | 0 | 271 | | GeneralUtilities.write_message_to_stderr(GeneralUtilities.get_advanced_errormessage_for_os_error(exception)) |
| | 0 | 272 | | if current_traceback is not None: |
| | 0 | 273 | | GeneralUtilities.write_message_to_stderr("Traceback: " + current_traceback.format_exc()) |
| | 0 | 274 | | GeneralUtilities.write_message_to_stderr(")") |
| | | 275 | | |
| | 1 | 276 | | @staticmethod |
| | 1 | 277 | | @check_arguments |
| | 1 | 278 | | def string_has_content(string: str) -> bool: |
| | 1 | 279 | | if string is None: |
| | 1 | 280 | | return False |
| | | 281 | | else: |
| | 1 | 282 | | return len(string) > 0 |
| | | 283 | | |
| | 1 | 284 | | @staticmethod |
| | 1 | 285 | | @check_arguments |
| | 1 | 286 | | def datetime_to_string_for_logfile_name(datetime_object: datetime) -> str: |
| | 0 | 287 | | return datetime_object.strftime('%Y-%m-%d_%H-%M-%S') |
| | | 288 | | |
| | 1 | 289 | | @staticmethod |
| | 1 | 290 | | @check_arguments |
| | 1 | 291 | | def datetime_to_string_for_logfile_entry(datetime_object: datetime) -> str: |
| | 0 | 292 | | return datetime_object.strftime('%Y-%m-%d %H:%M:%S') |
| | | 293 | | |
| | 1 | 294 | | @staticmethod |
| | 1 | 295 | | @check_arguments |
| | 1 | 296 | | def string_has_nonwhitespace_content(string: str) -> bool: |
| | 0 | 297 | | if string is None: |
| | 0 | 298 | | return False |
| | | 299 | | else: |
| | 0 | 300 | | return len(string.strip()) > 0 |
| | | 301 | | |
| | 1 | 302 | | @staticmethod |
| | 1 | 303 | | @check_arguments |
| | 1 | 304 | | def string_is_none_or_empty(argument: str) -> bool: |
| | 1 | 305 | | if argument is None: |
| | 1 | 306 | | return True |
| | 1 | 307 | | type_of_argument = type(argument) |
| | 1 | 308 | | if type_of_argument == str: |
| | 1 | 309 | | return argument == "" |
| | | 310 | | else: |
| | 0 | 311 | | raise ValueError(f"expected string-variable in argument of string_is_none_or_empty but the type was '{str(ty |
| | | 312 | | |
| | 1 | 313 | | @staticmethod |
| | 1 | 314 | | @check_arguments |
| | 1 | 315 | | def string_is_none_or_whitespace(string: str) -> bool: |
| | 1 | 316 | | if GeneralUtilities.string_is_none_or_empty(string): |
| | 1 | 317 | | return True |
| | | 318 | | else: |
| | 1 | 319 | | return string.strip() == "" |
| | | 320 | | |
| | 1 | 321 | | @staticmethod |
| | 1 | 322 | | @check_arguments |
| | 1 | 323 | | def strip_new_line_character(value: str) -> str: |
| | 1 | 324 | | while not GeneralUtilities.__strip_new_line_character_helper_value_is_ok(value): |
| | 1 | 325 | | value = GeneralUtilities.__strip_new_line_character_helper_normalize_value(value) |
| | 1 | 326 | | return value |
| | | 327 | | |
| | 1 | 328 | | @staticmethod |
| | 1 | 329 | | @check_arguments |
| | 1 | 330 | | def __strip_new_line_character_helper_value_is_ok(value: str) -> bool: |
| | 1 | 331 | | if value.startswith("\r") or value.endswith("\r"): |
| | 1 | 332 | | return False |
| | 1 | 333 | | if value.startswith("\n") or value.endswith("\n"): |
| | 0 | 334 | | return False |
| | 1 | 335 | | return True |
| | | 336 | | |
| | 1 | 337 | | @staticmethod |
| | 1 | 338 | | @check_arguments |
| | 1 | 339 | | def __strip_new_line_character_helper_normalize_value(value: str) -> str: |
| | 1 | 340 | | return value.strip('\n').strip('\r') |
| | | 341 | | |
| | 1 | 342 | | @staticmethod |
| | 1 | 343 | | @check_arguments |
| | 1 | 344 | | def file_ends_with_newline(file: str) -> bool: |
| | 0 | 345 | | with open(file, "rb") as file_object: |
| | 0 | 346 | | return GeneralUtilities.ends_with_newline_character(file_object.read()) |
| | | 347 | | |
| | 1 | 348 | | @staticmethod |
| | 1 | 349 | | @check_arguments |
| | 1 | 350 | | def ends_with_newline_character(content: bytes) -> bool: |
| | 1 | 351 | | return content.endswith(b'\x0a') |
| | | 352 | | |
| | 1 | 353 | | @staticmethod |
| | 1 | 354 | | @check_arguments |
| | 1 | 355 | | def __get_new_line_character_if_required(file: str) -> bool: |
| | 0 | 356 | | content = GeneralUtilities.read_binary_from_file(file) |
| | 0 | 357 | | if len(content) == 0: |
| | 0 | 358 | | return "" |
| | | 359 | | else: |
| | 0 | 360 | | if GeneralUtilities.ends_with_newline_character(content): |
| | 0 | 361 | | return "" |
| | | 362 | | else: |
| | 0 | 363 | | return "\n" |
| | | 364 | | |
| | 1 | 365 | | @staticmethod |
| | 1 | 366 | | @check_arguments |
| | 1 | 367 | | def append_line_to_file(file: str, line: str, encoding: str = "utf-8") -> None: |
| | 0 | 368 | | line = GeneralUtilities.__get_new_line_character_if_required(file)+line |
| | 0 | 369 | | GeneralUtilities.append_to_file(file, line, encoding) |
| | | 370 | | |
| | 1 | 371 | | @staticmethod |
| | 1 | 372 | | @check_arguments |
| | 1 | 373 | | def append_to_file(file: str, content: str, encoding: str = "utf-8") -> None: |
| | 0 | 374 | | with open(file, "a", encoding=encoding) as fileObject: |
| | 0 | 375 | | fileObject.write(content) |
| | | 376 | | |
| | 1 | 377 | | @staticmethod |
| | 1 | 378 | | @check_arguments |
| | 1 | 379 | | def ensure_directory_exists(path: str) -> None: |
| | 1 | 380 | | if not os.path.isdir(path): |
| | 1 | 381 | | os.makedirs(path) |
| | | 382 | | |
| | 1 | 383 | | @staticmethod |
| | 1 | 384 | | @check_arguments |
| | 1 | 385 | | def ensure_file_exists(path: str) -> None: |
| | 1 | 386 | | if (not os.path.isfile(path)): |
| | 1 | 387 | | with open(path, "a+", encoding="utf-8"): |
| | 1 | 388 | | pass |
| | | 389 | | |
| | 1 | 390 | | @staticmethod |
| | 1 | 391 | | @check_arguments |
| | 1 | 392 | | def __remove_readonly(func, path, _): |
| | 0 | 393 | | os.chmod(path, stat.S_IWRITE) |
| | 0 | 394 | | func(path) |
| | | 395 | | |
| | 1 | 396 | | @staticmethod |
| | 1 | 397 | | @check_arguments |
| | 1 | 398 | | def __rmtree(directory: str) -> None: |
| | 1 | 399 | | shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly) # pylint: disable=deprecated-argument |
| | | 400 | | |
| | 1 | 401 | | @staticmethod |
| | 1 | 402 | | @check_arguments |
| | 1 | 403 | | def ensure_directory_does_not_exist(path: str) -> None: |
| | 1 | 404 | | if (os.path.isdir(path)): |
| | 1 | 405 | | for root, dirs, files in os.walk(path, topdown=False): |
| | 1 | 406 | | for name in files: |
| | 1 | 407 | | filename = os.path.join(root, name) |
| | 1 | 408 | | os.chmod(filename, stat.S_IWUSR) |
| | 1 | 409 | | os.remove(filename) |
| | 1 | 410 | | for name in dirs: |
| | 1 | 411 | | GeneralUtilities.__rmtree(os.path.join(root, name)) |
| | 1 | 412 | | GeneralUtilities.__rmtree(path) |
| | | 413 | | |
| | 1 | 414 | | @staticmethod |
| | 1 | 415 | | @check_arguments |
| | 1 | 416 | | def ensure_folder_exists_and_is_empty(path: str) -> None: |
| | 0 | 417 | | GeneralUtilities.ensure_directory_exists(path) |
| | 0 | 418 | | for filename in os.listdir(path): |
| | 0 | 419 | | file_path = os.path.join(path, filename) |
| | 0 | 420 | | if os.path.isfile(file_path): |
| | 0 | 421 | | os.remove(file_path) |
| | 0 | 422 | | if os.path.islink(file_path): |
| | 0 | 423 | | os.unlink(file_path) |
| | 0 | 424 | | elif os.path.isdir(file_path): |
| | 0 | 425 | | shutil.rmtree(file_path) |
| | | 426 | | |
| | 1 | 427 | | @staticmethod |
| | 1 | 428 | | @check_arguments |
| | 1 | 429 | | def ensure_file_does_not_exist(path: str) -> None: |
| | 0 | 430 | | if (os.path.isfile(path)): |
| | 0 | 431 | | os.remove(path) |
| | | 432 | | |
| | 1 | 433 | | @staticmethod |
| | 1 | 434 | | @check_arguments |
| | 1 | 435 | | def format_xml_file(filepath: str) -> None: |
| | 0 | 436 | | GeneralUtilities.format_xml_file_with_encoding(filepath, "utf-8") |
| | | 437 | | |
| | 1 | 438 | | @staticmethod |
| | 1 | 439 | | @check_arguments |
| | 1 | 440 | | def format_xml_file_with_encoding(filepath: str, encoding: str) -> None: |
| | 0 | 441 | | with codecs.open(filepath, 'r', encoding=encoding) as file: |
| | 0 | 442 | | text = file.read() |
| | 0 | 443 | | text = parse(text).toprettyxml() |
| | 0 | 444 | | with codecs.open(filepath, 'w', encoding=encoding) as file: |
| | 0 | 445 | | file.write(text) |
| | | 446 | | |
| | 1 | 447 | | @staticmethod |
| | 1 | 448 | | @check_arguments |
| | 1 | 449 | | def get_clusters_and_sectors_of_disk(diskpath: str) -> None: |
| | 0 | 450 | | sectorsPerCluster = ctypes.c_ulonglong(0) |
| | 0 | 451 | | bytesPerSector = ctypes.c_ulonglong(0) |
| | 0 | 452 | | rootPathName = ctypes.c_wchar_p(diskpath) |
| | 0 | 453 | | ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, ctypes.pointer(sectorsPerCluster), ctypes.pointer(bytesPe |
| | 0 | 454 | | return (sectorsPerCluster.value, bytesPerSector.value) |
| | | 455 | | |
| | 1 | 456 | | @staticmethod |
| | 1 | 457 | | @check_arguments |
| | 1 | 458 | | def ensure_path_is_not_quoted(path: str) -> str: |
| | 0 | 459 | | if (path.startswith("\"") and path.endswith("\"")) or (path.startswith("'") and path.endswith("'")): |
| | 0 | 460 | | path = path[1:] |
| | 0 | 461 | | path = path[:-1] |
| | 0 | 462 | | return path |
| | | 463 | | else: |
| | 0 | 464 | | return path |
| | | 465 | | |
| | 1 | 466 | | @staticmethod |
| | 1 | 467 | | @check_arguments |
| | 1 | 468 | | def get_missing_files(folderA: str, folderB: str) -> list: |
| | 0 | 469 | | folderA_length = len(folderA) |
| | 0 | 470 | | result = [] |
| | 0 | 471 | | for fileA in GeneralUtilities.absolute_file_paths(folderA): |
| | 0 | 472 | | file = fileA[folderA_length:] |
| | 0 | 473 | | fileB = folderB + file |
| | 0 | 474 | | if not os.path.isfile(fileB): |
| | 0 | 475 | | result.append(fileB) |
| | 0 | 476 | | return result |
| | | 477 | | |
| | 1 | 478 | | @staticmethod |
| | 1 | 479 | | @check_arguments |
| | 1 | 480 | | def to_pascal_case(s: str) -> str: |
| | 1 | 481 | | return ''.join(current.lower() if prev.isalnum() else current.upper() for prev, current in zip(' ' + s, s) if cu |
| | | 482 | | |
| | 1 | 483 | | @staticmethod |
| | 1 | 484 | | @check_arguments |
| | 1 | 485 | | def find_between(s: str, start: str, end: str) -> str: |
| | 1 | 486 | | return s.split(start)[1].split(end)[0] |
| | | 487 | | |
| | 1 | 488 | | @staticmethod |
| | 1 | 489 | | @check_arguments |
| | 1 | 490 | | def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None: |
| | 1 | 491 | | lines = [GeneralUtilities.strip_new_line_character(line) for line in lines] |
| | 1 | 492 | | content = os.linesep.join(lines) |
| | 1 | 493 | | GeneralUtilities.write_text_to_file(file, content, encoding) |
| | | 494 | | |
| | 1 | 495 | | @staticmethod |
| | 1 | 496 | | @check_arguments |
| | 1 | 497 | | def write_text_to_file(file: str, content: str, encoding="utf-8") -> None: |
| | 1 | 498 | | GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding))) |
| | | 499 | | |
| | 1 | 500 | | @staticmethod |
| | 1 | 501 | | @check_arguments |
| | 1 | 502 | | def write_binary_to_file(file: str, content: bytes) -> None: |
| | 1 | 503 | | with open(file, "wb") as file_object: |
| | 1 | 504 | | file_object.write(content) |
| | | 505 | | |
| | 1 | 506 | | @staticmethod |
| | 1 | 507 | | @check_arguments |
| | 1 | 508 | | def read_lines_from_file(file: str, encoding="utf-8") -> list[str]: |
| | 1 | 509 | | return [GeneralUtilities.strip_new_line_character(line) for line in GeneralUtilities.read_text_from_file(file, e |
| | | 510 | | |
| | 1 | 511 | | @staticmethod |
| | 1 | 512 | | @check_arguments |
| | 1 | 513 | | def read_text_from_file(file: str, encoding="utf-8") -> str: |
| | 1 | 514 | | return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding) |
| | | 515 | | |
| | 1 | 516 | | @staticmethod |
| | 1 | 517 | | @check_arguments |
| | 1 | 518 | | def read_binary_from_file(file: str) -> bytes: |
| | 1 | 519 | | with open(file, "rb") as file_object: |
| | 1 | 520 | | return file_object.read() |
| | | 521 | | |
| | 1 | 522 | | @staticmethod |
| | 1 | 523 | | @check_arguments |
| | 1 | 524 | | def timedelta_to_simple_string(delta: timedelta) -> str: |
| | 0 | 525 | | return (datetime(1970, 1, 1, 0, 0, 0) + delta).strftime('%H:%M:%S') |
| | | 526 | | |
| | 1 | 527 | | @staticmethod |
| | 1 | 528 | | @check_arguments |
| | 1 | 529 | | def resolve_relative_path_from_current_working_directory(path: str) -> str: |
| | 1 | 530 | | return GeneralUtilities.resolve_relative_path(path, os.getcwd()) |
| | | 531 | | |
| | 1 | 532 | | @staticmethod |
| | 1 | 533 | | @check_arguments |
| | 1 | 534 | | def resolve_relative_path(path: str, base_path: str): |
| | 1 | 535 | | if (os.path.isabs(path)): |
| | 1 | 536 | | return path |
| | | 537 | | else: |
| | 1 | 538 | | return str(Path(os.path.join(base_path, path)).resolve()) |
| | | 539 | | |
| | 1 | 540 | | @staticmethod |
| | 1 | 541 | | @check_arguments |
| | 1 | 542 | | def get_metadata_for_file_for_clone_folder_structure(file: str) -> str: |
| | 0 | 543 | | size = os.path.getsize(file) |
| | 0 | 544 | | last_modified_timestamp = os.path.getmtime(file) |
| | 0 | 545 | | hash_value = GeneralUtilities.get_sha256_of_file(file) |
| | 0 | 546 | | last_access_timestamp = os.path.getatime(file) |
| | 0 | 547 | | return f'{{"size":"{size}","sha256":"{hash_value}","mtime":"{last_modified_timestamp}","atime":"{last_access_tim |
| | | 548 | | |
| | 1 | 549 | | @staticmethod |
| | 1 | 550 | | @check_arguments |
| | 1 | 551 | | def clone_folder_structure(source: str, target: str, copy_only_metadata: bool): |
| | 0 | 552 | | source = GeneralUtilities.resolve_relative_path(source, os.getcwd()) |
| | 0 | 553 | | target = GeneralUtilities.resolve_relative_path(target, os.getcwd()) |
| | 0 | 554 | | length_of_source = len(source) |
| | 0 | 555 | | for source_file in GeneralUtilities.absolute_file_paths(source): |
| | 0 | 556 | | target_file = target+source_file[length_of_source:] |
| | 0 | 557 | | GeneralUtilities.ensure_directory_exists(os.path.dirname(target_file)) |
| | 0 | 558 | | if copy_only_metadata: |
| | 0 | 559 | | with open(target_file, 'w', encoding='utf8') as f: |
| | 0 | 560 | | f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file)) |
| | | 561 | | else: |
| | 0 | 562 | | copyfile(source_file, target_file) |
| | | 563 | | |
| | 1 | 564 | | @staticmethod |
| | 1 | 565 | | @check_arguments |
| | 1 | 566 | | def current_user_has_elevated_privileges() -> bool: |
| | 0 | 567 | | try: |
| | 0 | 568 | | return os.getuid() == 0 |
| | 0 | 569 | | except AttributeError: |
| | 0 | 570 | | return ctypes.windll.shell32.IsUserAnAdmin() == 1 |
| | | 571 | | |
| | 1 | 572 | | @staticmethod |
| | 1 | 573 | | @check_arguments |
| | 1 | 574 | | def ensure_elevated_privileges() -> None: |
| | 0 | 575 | | if (not GeneralUtilities.current_user_has_elevated_privileges()): |
| | 0 | 576 | | raise ValueError("Not enough privileges.") |
| | | 577 | | |
| | 1 | 578 | | @staticmethod |
| | 1 | 579 | | @check_arguments |
| | 1 | 580 | | def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=F |
| | 0 | 581 | | for file in GeneralUtilities.get_direct_files_of_folder(folder): |
| | 0 | 582 | | GeneralUtilities.replace_in_filename(file, replace_from, replace_to, replace_only_full_match) |
| | 0 | 583 | | for sub_folder in GeneralUtilities.get_direct_folders_of_folder(folder): |
| | 0 | 584 | | GeneralUtilities.rename_names_of_all_files_and_folders(sub_folder, replace_from, replace_to, replace_only_fu |
| | 0 | 585 | | GeneralUtilities.replace_in_foldername(folder, replace_from, replace_to, replace_only_full_match) |
| | | 586 | | |
| | 1 | 587 | | @staticmethod |
| | 1 | 588 | | @check_arguments |
| | 1 | 589 | | def get_direct_files_of_folder(folder: str) -> list[str]: |
| | 1 | 590 | | result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))] |
| | 1 | 591 | | result = sorted(result, key=str.casefold) |
| | 1 | 592 | | return result |
| | | 593 | | |
| | 1 | 594 | | @staticmethod |
| | 1 | 595 | | @check_arguments |
| | 1 | 596 | | def get_direct_folders_of_folder(folder: str) -> list[str]: |
| | 1 | 597 | | result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))] |
| | 1 | 598 | | result = sorted(result, key=str.casefold) |
| | 1 | 599 | | return result |
| | | 600 | | |
| | 1 | 601 | | @staticmethod |
| | 1 | 602 | | @check_arguments |
| | 1 | 603 | | def get_all_files_of_folder(folder: str) -> list[str]: |
| | 1 | 604 | | result = list() |
| | 1 | 605 | | result.extend(GeneralUtilities.get_direct_files_of_folder(folder)) |
| | 1 | 606 | | for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder): |
| | 1 | 607 | | result.extend(GeneralUtilities.get_all_files_of_folder(subfolder)) |
| | 1 | 608 | | result = sorted(result, key=str.casefold) |
| | 1 | 609 | | return result |
| | | 610 | | |
| | 1 | 611 | | @staticmethod |
| | 1 | 612 | | @check_arguments |
| | 1 | 613 | | def get_all_folders_of_folder(folder: str) -> list[str]: |
| | 1 | 614 | | result = list() |
| | 1 | 615 | | subfolders = GeneralUtilities.get_direct_folders_of_folder(folder) |
| | 1 | 616 | | result.extend(subfolders) |
| | 1 | 617 | | for subfolder in subfolders: |
| | 1 | 618 | | result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder)) |
| | 1 | 619 | | result = sorted(result, key=str.casefold) |
| | 1 | 620 | | return result |
| | | 621 | | |
| | 1 | 622 | | @staticmethod |
| | 1 | 623 | | @check_arguments |
| | 1 | 624 | | def get_all_objects_of_folder(folder: str) -> list[str]: |
| | 0 | 625 | | return sorted(GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(fold |
| | | 626 | | |
| | 1 | 627 | | @staticmethod |
| | 1 | 628 | | @check_arguments |
| | 1 | 629 | | def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False): |
| | 0 | 630 | | filename = Path(file).name |
| | 0 | 631 | | if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)): |
| | 0 | 632 | | folder_of_file = os.path.dirname(file) |
| | 0 | 633 | | os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to))) |
| | | 634 | | |
| | 1 | 635 | | @staticmethod |
| | 1 | 636 | | @check_arguments |
| | 1 | 637 | | def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False): |
| | 0 | 638 | | foldername = Path(folder).name |
| | 0 | 639 | | if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)): |
| | 0 | 640 | | folder_of_folder = os.path.dirname(folder) |
| | 0 | 641 | | os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to))) |
| | | 642 | | |
| | 1 | 643 | | @staticmethod |
| | 1 | 644 | | @check_arguments |
| | 1 | 645 | | def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool: |
| | 0 | 646 | | if replace_only_full_match: |
| | 0 | 647 | | return input_text == search_text |
| | | 648 | | else: |
| | 0 | 649 | | return search_text in input_text |
| | | 650 | | |
| | 1 | 651 | | @staticmethod |
| | 1 | 652 | | @check_arguments |
| | 1 | 653 | | def str_none_safe(variable) -> str: |
| | 1 | 654 | | if variable is None: |
| | 0 | 655 | | return '' |
| | | 656 | | else: |
| | 1 | 657 | | return str(variable) |
| | | 658 | | |
| | 1 | 659 | | @staticmethod |
| | 1 | 660 | | @check_arguments |
| | 1 | 661 | | def arguments_to_array(arguments_as_string: str) -> list[str]: |
| | 1 | 662 | | if arguments_as_string is None: |
| | 0 | 663 | | return [] |
| | 1 | 664 | | if GeneralUtilities.string_has_content(arguments_as_string): |
| | 1 | 665 | | return arguments_as_string.split(" ") # TODO this function should get improved to allow whitespaces in quot |
| | | 666 | | else: |
| | 0 | 667 | | return [] |
| | | 668 | | |
| | 1 | 669 | | @staticmethod |
| | 1 | 670 | | @check_arguments |
| | 1 | 671 | | def arguments_to_array_for_log(arguments_as_string: str) -> list[str]: |
| | 0 | 672 | | if arguments_as_string is None: |
| | 0 | 673 | | return None |
| | 0 | 674 | | return GeneralUtilities.arguments_to_array(arguments_as_string) |
| | | 675 | | |
| | 1 | 676 | | @staticmethod |
| | 1 | 677 | | @check_arguments |
| | 1 | 678 | | def get_sha256_of_file(file: str) -> str: |
| | 0 | 679 | | sha256 = hashlib.sha256() |
| | 0 | 680 | | with open(file, "rb") as fileObject: |
| | 0 | 681 | | for chunk in iter(lambda: fileObject.read(4096), b""): |
| | 0 | 682 | | sha256.update(chunk) |
| | 0 | 683 | | return sha256.hexdigest() |
| | | 684 | | |
| | 1 | 685 | | @staticmethod |
| | 1 | 686 | | @check_arguments |
| | 1 | 687 | | def remove_duplicates(input_list) -> list: |
| | 1 | 688 | | result = [] |
| | 1 | 689 | | for item in input_list: |
| | 1 | 690 | | if not item in result: |
| | 1 | 691 | | result.append(item) |
| | 1 | 692 | | return result |
| | | 693 | | |
| | 1 | 694 | | @staticmethod |
| | 1 | 695 | | @check_arguments |
| | 1 | 696 | | def print_stacktrace() -> None: |
| | 0 | 697 | | for line in traceback.format_stack(): |
| | 0 | 698 | | GeneralUtilities.write_message_to_stdout(line.strip()) |
| | | 699 | | |
| | 1 | 700 | | @staticmethod |
| | 1 | 701 | | @check_arguments |
| | 1 | 702 | | def string_to_boolean(value: str) -> bool: |
| | 0 | 703 | | value = value.strip().lower() |
| | 0 | 704 | | if value in ('yes', 'y', 'true', 't', '1'): |
| | 0 | 705 | | return True |
| | 0 | 706 | | elif value in ('no', 'n', 'false', 'f', '0'): |
| | 0 | 707 | | return False |
| | | 708 | | else: |
| | 0 | 709 | | raise ValueError(f"Can not convert '{value}' to a boolean value") |
| | | 710 | | |
| | 1 | 711 | | @staticmethod |
| | 1 | 712 | | @check_arguments |
| | 1 | 713 | | def file_is_empty(file: str) -> bool: |
| | 0 | 714 | | return os.stat(file).st_size == 0 |
| | | 715 | | |
| | 1 | 716 | | @staticmethod |
| | 1 | 717 | | @check_arguments |
| | 1 | 718 | | def folder_is_empty(folder: str) -> bool: |
| | 0 | 719 | | return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders |
| | | 720 | | |
| | 1 | 721 | | @staticmethod |
| | 1 | 722 | | @check_arguments |
| | 1 | 723 | | def get_time_based_logfile_by_folder(folder: str, name: str = "Log", in_utc: bool = False) -> str: |
| | 0 | 724 | | return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUti |
| | | 725 | | |
| | 1 | 726 | | @staticmethod |
| | 1 | 727 | | @check_arguments |
| | 1 | 728 | | def get_time_based_logfilename(name: str = "Log", in_utc: bool = False) -> str: |
| | 0 | 729 | | if (in_utc): |
| | 0 | 730 | | d = datetime.utcnow() |
| | | 731 | | else: |
| | 0 | 732 | | d = datetime.now() |
| | 0 | 733 | | return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}" |
| | | 734 | | |
| | 1 | 735 | | @staticmethod |
| | 1 | 736 | | @check_arguments |
| | 1 | 737 | | def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str: |
| | 1 | 738 | | return payload.decode(encoding, errors="ignore") |
| | | 739 | | |
| | 1 | 740 | | @staticmethod |
| | 1 | 741 | | @check_arguments |
| | 1 | 742 | | def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes: |
| | 0 | 743 | | return payload.encode(encoding, errors="ignore") |
| | | 744 | | |
| | 1 | 745 | | @staticmethod |
| | 1 | 746 | | @check_arguments |
| | 1 | 747 | | def contains_line(lines, regex: str) -> bool: |
| | 0 | 748 | | for line in lines: |
| | 0 | 749 | | if (re.match(regex, line)): |
| | 0 | 750 | | return True |
| | 0 | 751 | | return False |
| | | 752 | | |
| | 1 | 753 | | @staticmethod |
| | 1 | 754 | | @check_arguments |
| | 1 | 755 | | def read_csv_file(file: str, ignore_first_line: bool = False, treat_number_sign_at_begin_of_line_as_comment: bool = |
| | 0 | 756 | | lines = GeneralUtilities.read_lines_from_file(file, encoding) |
| | | 757 | | |
| | 0 | 758 | | if ignore_first_line: |
| | 0 | 759 | | lines = lines[1:] |
| | 0 | 760 | | result = list() |
| | | 761 | | line: str |
| | 0 | 762 | | for line_loopvariable in lines: |
| | 0 | 763 | | use_line = True |
| | 0 | 764 | | line = line_loopvariable |
| | | 765 | | |
| | 0 | 766 | | if trim_values: |
| | 0 | 767 | | line = line.strip() |
| | 0 | 768 | | if ignore_empty_lines: |
| | 0 | 769 | | if not GeneralUtilities.string_has_content(line): |
| | 0 | 770 | | use_line = False |
| | | 771 | | |
| | 0 | 772 | | if treat_number_sign_at_begin_of_line_as_comment: |
| | 0 | 773 | | if line.startswith("#"): |
| | 0 | 774 | | use_line = False |
| | | 775 | | |
| | 0 | 776 | | if use_line: |
| | 0 | 777 | | if separator_character in line: |
| | 0 | 778 | | raw_values_of_line = GeneralUtilities.to_list(line, separator_character) |
| | | 779 | | else: |
| | 0 | 780 | | raw_values_of_line = [line] |
| | 0 | 781 | | if trim_values: |
| | 0 | 782 | | raw_values_of_line = [value.strip() for value in raw_values_of_line] |
| | 0 | 783 | | values_of_line = [] |
| | 0 | 784 | | for raw_value_of_line in raw_values_of_line: |
| | 0 | 785 | | value_of_line = raw_value_of_line |
| | 0 | 786 | | if values_are_surrounded_by_quotes: |
| | 0 | 787 | | value_of_line = value_of_line[1:] |
| | 0 | 788 | | value_of_line = value_of_line[:-1] |
| | 0 | 789 | | value_of_line = value_of_line.replace('""', '"') |
| | 0 | 790 | | values_of_line.append(value_of_line) |
| | 0 | 791 | | result.extend([values_of_line]) |
| | 0 | 792 | | return result |
| | | 793 | | |
| | 1 | 794 | | @staticmethod |
| | 1 | 795 | | @check_arguments |
| | 1 | 796 | | def epew_is_available() -> bool: |
| | 0 | 797 | | return GeneralUtilities.tool_is_available("epew") |
| | | 798 | | |
| | 1 | 799 | | @staticmethod |
| | 1 | 800 | | @check_arguments |
| | 1 | 801 | | def tool_is_available(toolname: str) -> bool: |
| | 0 | 802 | | try: |
| | 0 | 803 | | return shutil.which(toolname) is not None |
| | 0 | 804 | | except: |
| | 0 | 805 | | return False |
| | | 806 | | |
| | 1 | 807 | | @staticmethod |
| | 1 | 808 | | @check_arguments |
| | 1 | 809 | | @deprecated |
| | 1 | 810 | | def absolute_file_paths(directory: str) -> list[str]: |
| | 0 | 811 | | return GeneralUtilities.get_all_files_of_folder(directory) |
| | | 812 | | |
| | 1 | 813 | | @staticmethod |
| | 1 | 814 | | @check_arguments |
| | 1 | 815 | | def to_list(list_as_string: str, separator: str = ",") -> list[str]: |
| | 1 | 816 | | result = list() |
| | 1 | 817 | | if list_as_string is not None: |
| | 1 | 818 | | list_as_string = list_as_string.strip() |
| | 1 | 819 | | if list_as_string == "": |
| | 1 | 820 | | pass |
| | 1 | 821 | | elif separator in list_as_string: |
| | 1 | 822 | | for item in list_as_string.split(separator): |
| | 1 | 823 | | result.append(item.strip()) |
| | | 824 | | else: |
| | 1 | 825 | | result.append(list_as_string) |
| | 1 | 826 | | return result |
| | | 827 | | |
| | 1 | 828 | | @staticmethod |
| | 1 | 829 | | @check_arguments |
| | 1 | 830 | | def get_next_square_number(number: int) -> int: |
| | 1 | 831 | | GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative number |
| | 1 | 832 | | if number == 0: |
| | 1 | 833 | | return 1 |
| | 1 | 834 | | root = 0 |
| | 1 | 835 | | square = 0 |
| | 1 | 836 | | while square < number: |
| | 1 | 837 | | root = root+1 |
| | 1 | 838 | | square = root*root |
| | 1 | 839 | | return root*root |
| | | 840 | | |
| | 1 | 841 | | @staticmethod |
| | 1 | 842 | | @check_arguments |
| | 1 | 843 | | def generate_password(length: int = 16, alphabet: str = None) -> None: |
| | 0 | 844 | | if alphabet is None: |
| | 0 | 845 | | alphabet = strin.ascii_letters + strin.digits+"_" |
| | 0 | 846 | | return ''.join(secrets.choice(alphabet) for i in range(length)) |
| | | 847 | | |
| | 1 | 848 | | @staticmethod |
| | 1 | 849 | | @check_arguments |
| | 1 | 850 | | def assert_condition(condition: bool, information: str = None) -> None: |
| | | 851 | | """Throws an exception if the condition is false.""" |
| | 1 | 852 | | if (not condition): |
| | 0 | 853 | | if information is None: |
| | 0 | 854 | | information = "Internal assertion error." |
| | 0 | 855 | | raise ValueError("Condition failed. "+information) |
| | | 856 | | |
| | 1 | 857 | | @staticmethod |
| | 1 | 858 | | def current_system_is_windows(): |
| | 0 | 859 | | return platform.system() == 'Windows' |
| | | 860 | | |
| | 1 | 861 | | @staticmethod |
| | 1 | 862 | | def current_system_is_linux(): |
| | 0 | 863 | | return platform.system() == 'Linux' |
| | | 864 | | |
| | 1 | 865 | | @staticmethod |
| | 1 | 866 | | @check_arguments |
| | 1 | 867 | | def get_certificate_expiry_date(certificate_file: str) -> datetime: |
| | 0 | 868 | | with open(certificate_file, encoding="utf-8") as certificate_file_content: |
| | 0 | 869 | | cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read()) |
| | 0 | 870 | | date_as_bytes = cert.get_notAfter() |
| | 0 | 871 | | date_as_string = date_as_bytes.decode("utf-8") |
| | 0 | 872 | | result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ') |
| | 0 | 873 | | return result |
| | | 874 | | |
| | 1 | 875 | | @staticmethod |
| | 1 | 876 | | @check_arguments |
| | 1 | 877 | | def certificate_is_expired(certificate_file: str) -> bool: |
| | 0 | 878 | | return GeneralUtilities.get_certificate_expiry_date(certificate_file) < datetime.now() |
| | | 879 | | |
| | 1 | 880 | | @staticmethod |
| | 1 | 881 | | @check_arguments |
| | 1 | 882 | | def internet_connection_is_available() -> bool: |
| | | 883 | | # TODO add more hosts to check to return true if at least one is available |
| | 0 | 884 | | try: |
| | 0 | 885 | | with urllib.request.urlopen("https://www.google.com") as url_result: |
| | 0 | 886 | | return (url_result.code // 100) == 2 |
| | 0 | 887 | | except: |
| | 0 | 888 | | pass |
| | 0 | 889 | | return False |
| | | 890 | | |
| | 1 | 891 | | @staticmethod |
| | 1 | 892 | | @check_arguments |
| | 1 | 893 | | def replace_variable_in_string(input_string: str, variable_name: str, variable_value: str) -> None: |
| | 0 | 894 | | return input_string.replace(f"__[{variable_name}]__", variable_value) |
| | | 895 | | |
| | 1 | 896 | | @staticmethod |
| | 1 | 897 | | @check_arguments |
| | 1 | 898 | | def input(prompt: str, print_result: bool) -> str: # This function is a workaround for usescases like python script |
| | 0 | 899 | | GeneralUtilities.write_message_to_stdout(prompt) |
| | 0 | 900 | | result: str = input() |
| | 0 | 901 | | if print_result: |
| | 0 | 902 | | GeneralUtilities.write_message_to_stdout(f"Result: {result}") |
| | 0 | 903 | | return result |
| | | 904 | | |
| | 1 | 905 | | @staticmethod |
| | 1 | 906 | | @check_arguments |
| | 1 | 907 | | def run_program_simple(program: str, arguments: list[str], cwd: str = None) -> tuple[int, str, str]: |
| | 0 | 908 | | if cwd is None: |
| | 0 | 909 | | cwd = os.getcwd() |
| | 0 | 910 | | cmd = [program]+arguments |
| | 0 | 911 | | with subprocess.Popen(cmd, cwd=cwd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as process: |
| | 0 | 912 | | stdout, stderr = process.communicate() |
| | 0 | 913 | | exit_code = process.wait() |
| | 0 | 914 | | return (exit_code, stdout, stderr) |
| | | 915 | | |
| | 1 | 916 | | @staticmethod |
| | 1 | 917 | | @check_arguments |
| | 1 | 918 | | def assert_file_exists(file: str) -> str: |
| | 0 | 919 | | GeneralUtilities.assert_condition(os.path.isfile(file), f"File '{file}' does not exist.") |
| | | 920 | | |
| | 1 | 921 | | @staticmethod |
| | 1 | 922 | | @check_arguments |
| | 1 | 923 | | def assert_folder_exists(folder: str) -> str: |
| | 0 | 924 | | GeneralUtilities.assert_condition(os.path.isdir(folder), f"Folder '{folder}' does not exist.") |
| | | 925 | | |
| | 1 | 926 | | @staticmethod |
| | 1 | 927 | | @check_arguments |
| | 1 | 928 | | def retry_action(action, amount_of_attempts: int) -> None: |
| | 0 | 929 | | amount_of_fails = 0 |
| | 0 | 930 | | enabled = True |
| | 0 | 931 | | while enabled: |
| | 0 | 932 | | try: |
| | 0 | 933 | | result=action() |
| | 0 | 934 | | return result |
| | 0 | 935 | | except Exception: |
| | 0 | 936 | | amount_of_fails = amount_of_fails+1 |
| | 0 | 937 | | GeneralUtilities.assert_condition(not (amount_of_attempts < amount_of_fails)) |
| | 0 | 938 | | if amount_of_fails == amount_of_attempts: |
| | 0 | 939 | | raise |
| | 0 | 940 | | return None |
| | | 941 | | |
| | 1 | 942 | | @staticmethod |
| | 1 | 943 | | @check_arguments |
| | 1 | 944 | | def int_to_string(number: int, leading_zeroplaces: int, trailing_zeroplaces: int) -> str: |
| | 1 | 945 | | return GeneralUtilities.float_to_string(float(number), leading_zeroplaces, trailing_zeroplaces) |
| | | 946 | | |
| | 1 | 947 | | @staticmethod |
| | 1 | 948 | | @check_arguments |
| | 1 | 949 | | def float_to_string(number: float, leading_zeroplaces: int, trailing_zeroplaces: int) -> str: |
| | 1 | 950 | | plain_str = str(number) |
| | 1 | 951 | | GeneralUtilities.assert_condition("." in plain_str) |
| | 1 | 952 | | splitted: list[str] = plain_str.split(".") |
| | 1 | 953 | | return splitted[0].zfill(leading_zeroplaces)+"."+splitted[1].ljust(trailing_zeroplaces, '0') |