< Summary - ScriptCollection

Information
Class: GeneralUtilities.py
Assembly: ScriptCollection
File(s): .\ScriptCollection\ScriptCollection\GeneralUtilities.py
Tag: v3.5.70
Line coverage
63%
Covered lines: 510
Uncovered lines: 296
Coverable lines: 806
Total lines: 953
Line coverage: 63.2%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

File(s)

.\ScriptCollection\ScriptCollection\GeneralUtilities.py

#LineLine coverage
 11import codecs
 12import platform
 13import inspect
 14import ctypes
 15import hashlib
 16import re
 17import os
 18import subprocess
 19import shutil
 110import urllib
 111import stat
 112import secrets
 113import string as strin
 114import sys
 115import traceback
 116import warnings
 117import functools
 118from datetime import datetime, timedelta, date
 119from os import listdir
 120from os.path import isfile, join, isdir
 121from pathlib import Path
 122from shutil import copyfile
 123import typing
 124from defusedxml.minidom import parse
 125from OpenSSL import crypto
 26
 27
 128class GeneralUtilities:
 29
 130    __datetime_format: str = "%Y-%m-%dT%H:%M:%S"
 131    __date_format: str = "%Y-%m-%d"
 32
 133    @staticmethod
 134    def get_modest_dark_url() -> str:
 035        return "https://aniondev.github.io/CDN/ScriptCollectionDesigns/ModestDark/Style.css"
 36
 137    @staticmethod
 138    def is_generic(t: typing.Type):
 139        return hasattr(t, "__origin__")
 40
 141    @staticmethod
 142    def check_arguments(function):
 143        def __check_function(*args, **named_args):
 144            parameters: list = inspect.getfullargspec(function)[0].copy()
 145            arguments: list = list(tuple(args)).copy()
 146            if "self" in parameters:
 147                parameters.remove("self")
 148                arguments.pop(0)
 149            for index, argument in enumerate(arguments):
 150                if argument is not None:  # Check type of None is not possible. None is always a valid argument-value
 151                    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.
 153                        if not GeneralUtilities.is_generic(function.__annotations__[parameters[index]]):
 154                            if not isinstance(argument, function.__annotations__[parameters[index]]):
 055                                raise TypeError(f"Argument with index {index} for function {function.__name__} ('{str(ar
 156            for index, named_argument in enumerate(named_args):
 157                if named_args[named_argument] is not None:
 158                    if parameters[index] in function.__annotations__:
 159                        if not GeneralUtilities.is_generic(function.__annotations__.get(named_argument)):
 160                            if not isinstance(named_args[named_argument], function.__annotations__.get(named_argument)):
 061                                raise TypeError(f"Argument with name {named_argument} for function {function.__name__} (
 162            return function(*args, **named_args)
 163        __check_function.__doc__ = function.__doc__
 164        return __check_function
 65
 166    @staticmethod
 167    def deprecated(func):
 168        @functools.wraps(func)
 169        def new_func(*args, **kwargs):
 070            warnings.simplefilter('always', DeprecationWarning)
 071            warnings.warn(f"Call to deprecated function {func.__name__}", category=DeprecationWarning, stacklevel=2)
 072            warnings.simplefilter('default', DeprecationWarning)
 073            return func(*args, **kwargs)
 174        return new_func
 75
 176    @staticmethod
 177    @check_arguments
 178    def args_array_surround_with_quotes_if_required(arguments: list[str]) -> list[str]:
 079        result = []
 080        for argument in arguments:
 081            if " " in argument and not (argument.startswith('"') and argument.endswith('"')):
 082                result.append(f'"{argument}"')
 83            else:
 084                result.append(argument)
 085        return result
 86
 187    @staticmethod
 188    @check_arguments
 189    def string_to_lines(string: str, add_empty_lines: bool = True, adapt_lines: bool = True) -> list[str]:
 190        result = list()
 191        if (string is not None):
 192            lines = list()
 193            if ("\n" in string):
 194                lines = string.split("\n")
 95            else:
 196                lines.append(string)
 197        for rawline in lines:
 198            if adapt_lines:
 199                line = rawline.replace("\r", "").strip()
 100            else:
 0101                line = rawline
 1102            if GeneralUtilities.string_is_none_or_whitespace(line):
 1103                if add_empty_lines:
 1104                    result.append(line)
 105            else:
 1106                result.append(line)
 1107        return result
 108
 1109    @staticmethod
 1110    @check_arguments
 1111    def string_to_datetime(value: str) -> datetime:
 1112        if "." in value:
 1113            value = value.split(".")[0]
 1114        return datetime.strptime(value, GeneralUtilities.__datetime_format)  # value ="2022-10-06T19:26:01" for example
 115
 1116    @staticmethod
 1117    @check_arguments
 1118    def datetime_to_string(value: datetime) -> str:
 1119        value = datetime(year=value.year, month=value.month, day=value.day, hour=value.hour, minute=value.minute, second
 1120        return value.strftime(GeneralUtilities.__datetime_format)  # returns "2022-10-06T19:26:01" for example
 121
 1122    @staticmethod
 1123    @check_arguments
 1124    def string_to_date(value: str) -> date:
 1125        splitted = value.split("-")
 1126        return date(int(splitted[0]), int(splitted[1]), int(splitted[2]))  # value ="2022-10-06" for example
 127
 1128    @staticmethod
 1129    @check_arguments
 1130    def date_to_string(value: date) -> str:
 1131        return value.strftime(GeneralUtilities.__date_format)  # returns "2022-10-06" for example
 132
 1133    @staticmethod
 1134    @check_arguments
 1135    def copy_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False) -> None:
 0136        GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, 
 137
 1138    @staticmethod
 1139    @check_arguments
 1140    def move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False) -> None:
 0141        GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, 
 142
 1143    @staticmethod
 1144    @check_arguments
 1145    def __copy_or_move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files, remove_
 0146        srcDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(source_directory)
 0147        dstDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(target_directory)
 0148        if (os.path.isdir(source_directory)):
 0149            GeneralUtilities.ensure_directory_exists(target_directory)
 0150            for file in GeneralUtilities.get_direct_files_of_folder(srcDirFull):
 0151                filename = os.path.basename(file)
 0152                targetfile = os.path.join(dstDirFull, filename)
 0153                if (os.path.isfile(targetfile)):
 0154                    if overwrite_existing_files:
 0155                        GeneralUtilities.ensure_file_does_not_exist(targetfile)
 156                    else:
 0157                        raise ValueError(f"Targetfile {targetfile} does already exist")
 0158                if remove_source:
 0159                    shutil.move(file, dstDirFull)
 160                else:
 0161                    shutil.copy(file, dstDirFull)
 0162            for sub_folder in GeneralUtilities.get_direct_folders_of_folder(srcDirFull):
 0163                foldername = os.path.basename(sub_folder)
 0164                sub_target = os.path.join(dstDirFull, foldername)
 0165                GeneralUtilities.__copy_or_move_content_of_folder(sub_folder, sub_target, overwrite_existing_files, remo
 0166                if remove_source:
 0167                    GeneralUtilities.ensure_directory_does_not_exist(sub_folder)
 168        else:
 0169            raise ValueError(f"Folder '{source_directory}' does not exist")
 170
 1171    @staticmethod
 1172    @check_arguments
 1173    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
 0176        if verbose:
 0177            GeneralUtilities.write_message_to_stdout(f"Replace '{replace_from_regex}' to '{replace_to_regex}' in '{file}
 0178        with open(file, encoding=encoding, mode="r") as f:
 0179            lines = f.readlines()
 0180            replaced_lines = []
 0181            for line in lines:
 0182                replaced_line = re.sub(replace_from_regex, replace_to_regex, line)
 0183                replaced_lines.append(replaced_line)
 0184        with open(file, encoding=encoding, mode="w") as f:
 0185            f.writelines(replaced_lines)
 186
 1187    @staticmethod
 1188    @check_arguments
 1189    def replace_regex_in_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8") -> None:
 0190        with open(file, encoding=encoding, mode="r") as f:
 0191            content = f.read()
 0192            content = re.sub(replace_from_regex, replace_to_regex, content)
 0193        with open(file, encoding=encoding, mode="w") as f:
 0194            f.write(content)
 195
 1196    @staticmethod
 1197    @check_arguments
 1198    def replace_xmltag_in_file(file: str, tag: str, new_value: str, encoding="utf-8") -> None:
 0199        GeneralUtilities.replace_regex_in_file(file, f"<{tag}>.*</{tag}>", f"<{tag}>{new_value}</{tag}>", encoding)
 200
 1201    @staticmethod
 1202    @check_arguments
 1203    def update_version_in_csproj_file(file: str, target_version: str) -> None:
 0204        GeneralUtilities.replace_xmltag_in_file(file, "Version", target_version)
 0205        GeneralUtilities.replace_xmltag_in_file(file, "AssemblyVersion", target_version + ".0")
 0206        GeneralUtilities.replace_xmltag_in_file(file, "FileVersion", target_version + ".0")
 207
 1208    @staticmethod
 1209    @check_arguments
 1210    def replace_underscores_in_text(text: str, replacements: dict) -> str:
 0211        changed = True
 0212        while changed:
 0213            changed = False
 0214            for key, value in replacements.items():
 0215                previousValue = text
 0216                text = text.replace(f"__{key}__", value)
 0217                if (not text == previousValue):
 0218                    changed = True
 0219        return text
 220
 1221    @staticmethod
 1222    @check_arguments
 1223    def replace_underscores_in_file(file: str, replacements: dict, encoding: str = "utf-8"):
 0224        text = GeneralUtilities.read_text_from_file(file, encoding)
 0225        text = GeneralUtilities.replace_underscores_in_text(text, replacements)
 0226        GeneralUtilities.write_text_to_file(file, text, encoding)
 227
 1228    @staticmethod
 1229    @check_arguments
 1230    def reconfigure_standrd_input_and_outputs():
 0231        sys.stdin.reconfigure(encoding='utf-8')
 0232        sys.stderr.reconfigure(encoding='utf-8')
 0233        sys.stdout.reconfigure(encoding='utf-8')
 234
 1235    @staticmethod
 1236    @check_arguments
 1237    def write_message_to_stdout(message: str):
 1238        for line in GeneralUtilities.string_to_lines(message):
 1239            sys.stdout.write(GeneralUtilities.str_none_safe(line)+"\n")
 1240            sys.stdout.flush()
 241
 1242    @staticmethod
 1243    @check_arguments
 1244    def write_message_to_stderr(message: str):
 0245        sys.stderr.write(GeneralUtilities.str_none_safe(message)+"\n")
 0246        sys.stderr.flush()
 247
 1248    @staticmethod
 1249    @check_arguments
 1250    def get_advanced_errormessage_for_os_error(os_error: OSError) -> str:
 1251        if GeneralUtilities.string_has_content(os_error.filename2):
 0252            secondpath = f" {os_error.filename2}"
 253        else:
 1254            secondpath = ""
 1255        return f"Related path(s): {os_error.filename}{secondpath}"
 256
 1257    @staticmethod
 1258    @check_arguments
 1259    def write_exception_to_stderr(exception: Exception, extra_message: str = None):
 0260        GeneralUtilities.write_exception_to_stderr_with_traceback(exception, None, extra_message)
 261
 1262    @staticmethod
 1263    @check_arguments
 1264    def write_exception_to_stderr_with_traceback(exception: Exception, current_traceback=None, extra_message: str = None
 0265        GeneralUtilities.write_message_to_stderr("Exception(")
 0266        GeneralUtilities.write_message_to_stderr("Type: " + str(type(exception)))
 0267        GeneralUtilities.write_message_to_stderr("Message: " + str(exception))
 0268        if extra_message is not None:
 0269            GeneralUtilities.write_message_to_stderr("Extra-message: " + str(extra_message))
 0270        if isinstance(exception, OSError):
 0271            GeneralUtilities.write_message_to_stderr(GeneralUtilities.get_advanced_errormessage_for_os_error(exception))
 0272        if current_traceback is not None:
 0273            GeneralUtilities.write_message_to_stderr("Traceback: " + current_traceback.format_exc())
 0274        GeneralUtilities.write_message_to_stderr(")")
 275
 1276    @staticmethod
 1277    @check_arguments
 1278    def string_has_content(string: str) -> bool:
 1279        if string is None:
 1280            return False
 281        else:
 1282            return len(string) > 0
 283
 1284    @staticmethod
 1285    @check_arguments
 1286    def datetime_to_string_for_logfile_name(datetime_object: datetime) -> str:
 0287        return datetime_object.strftime('%Y-%m-%d_%H-%M-%S')
 288
 1289    @staticmethod
 1290    @check_arguments
 1291    def datetime_to_string_for_logfile_entry(datetime_object: datetime) -> str:
 0292        return datetime_object.strftime('%Y-%m-%d %H:%M:%S')
 293
 1294    @staticmethod
 1295    @check_arguments
 1296    def string_has_nonwhitespace_content(string: str) -> bool:
 0297        if string is None:
 0298            return False
 299        else:
 0300            return len(string.strip()) > 0
 301
 1302    @staticmethod
 1303    @check_arguments
 1304    def string_is_none_or_empty(argument: str) -> bool:
 1305        if argument is None:
 1306            return True
 1307        type_of_argument = type(argument)
 1308        if type_of_argument == str:
 1309            return argument == ""
 310        else:
 0311            raise ValueError(f"expected string-variable in argument of string_is_none_or_empty but the type was '{str(ty
 312
 1313    @staticmethod
 1314    @check_arguments
 1315    def string_is_none_or_whitespace(string: str) -> bool:
 1316        if GeneralUtilities.string_is_none_or_empty(string):
 1317            return True
 318        else:
 1319            return string.strip() == ""
 320
 1321    @staticmethod
 1322    @check_arguments
 1323    def strip_new_line_character(value: str) -> str:
 1324        while not GeneralUtilities.__strip_new_line_character_helper_value_is_ok(value):
 1325            value = GeneralUtilities.__strip_new_line_character_helper_normalize_value(value)
 1326        return value
 327
 1328    @staticmethod
 1329    @check_arguments
 1330    def __strip_new_line_character_helper_value_is_ok(value: str) -> bool:
 1331        if value.startswith("\r") or value.endswith("\r"):
 1332            return False
 1333        if value.startswith("\n") or value.endswith("\n"):
 0334            return False
 1335        return True
 336
 1337    @staticmethod
 1338    @check_arguments
 1339    def __strip_new_line_character_helper_normalize_value(value: str) -> str:
 1340        return value.strip('\n').strip('\r')
 341
 1342    @staticmethod
 1343    @check_arguments
 1344    def file_ends_with_newline(file: str) -> bool:
 0345        with open(file, "rb") as file_object:
 0346            return GeneralUtilities.ends_with_newline_character(file_object.read())
 347
 1348    @staticmethod
 1349    @check_arguments
 1350    def ends_with_newline_character(content: bytes) -> bool:
 1351        return content.endswith(b'\x0a')
 352
 1353    @staticmethod
 1354    @check_arguments
 1355    def __get_new_line_character_if_required(file: str) -> bool:
 0356        content = GeneralUtilities.read_binary_from_file(file)
 0357        if len(content) == 0:
 0358            return ""
 359        else:
 0360            if GeneralUtilities.ends_with_newline_character(content):
 0361                return ""
 362            else:
 0363                return "\n"
 364
 1365    @staticmethod
 1366    @check_arguments
 1367    def append_line_to_file(file: str, line: str, encoding: str = "utf-8") -> None:
 0368        line = GeneralUtilities.__get_new_line_character_if_required(file)+line
 0369        GeneralUtilities.append_to_file(file, line, encoding)
 370
 1371    @staticmethod
 1372    @check_arguments
 1373    def append_to_file(file: str, content: str, encoding: str = "utf-8") -> None:
 0374        with open(file, "a", encoding=encoding) as fileObject:
 0375            fileObject.write(content)
 376
 1377    @staticmethod
 1378    @check_arguments
 1379    def ensure_directory_exists(path: str) -> None:
 1380        if not os.path.isdir(path):
 1381            os.makedirs(path)
 382
 1383    @staticmethod
 1384    @check_arguments
 1385    def ensure_file_exists(path: str) -> None:
 1386        if (not os.path.isfile(path)):
 1387            with open(path, "a+", encoding="utf-8"):
 1388                pass
 389
 1390    @staticmethod
 1391    @check_arguments
 1392    def __remove_readonly(func, path, _):
 0393        os.chmod(path, stat.S_IWRITE)
 0394        func(path)
 395
 1396    @staticmethod
 1397    @check_arguments
 1398    def __rmtree(directory: str) -> None:
 1399        shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly)  # pylint: disable=deprecated-argument
 400
 1401    @staticmethod
 1402    @check_arguments
 1403    def ensure_directory_does_not_exist(path: str) -> None:
 1404        if (os.path.isdir(path)):
 1405            for root, dirs, files in os.walk(path, topdown=False):
 1406                for name in files:
 1407                    filename = os.path.join(root, name)
 1408                    os.chmod(filename, stat.S_IWUSR)
 1409                    os.remove(filename)
 1410                for name in dirs:
 1411                    GeneralUtilities.__rmtree(os.path.join(root, name))
 1412            GeneralUtilities.__rmtree(path)
 413
 1414    @staticmethod
 1415    @check_arguments
 1416    def ensure_folder_exists_and_is_empty(path: str) -> None:
 0417        GeneralUtilities.ensure_directory_exists(path)
 0418        for filename in os.listdir(path):
 0419            file_path = os.path.join(path, filename)
 0420            if os.path.isfile(file_path):
 0421                os.remove(file_path)
 0422            if os.path.islink(file_path):
 0423                os.unlink(file_path)
 0424            elif os.path.isdir(file_path):
 0425                shutil.rmtree(file_path)
 426
 1427    @staticmethod
 1428    @check_arguments
 1429    def ensure_file_does_not_exist(path: str) -> None:
 0430        if (os.path.isfile(path)):
 0431            os.remove(path)
 432
 1433    @staticmethod
 1434    @check_arguments
 1435    def format_xml_file(filepath: str) -> None:
 0436        GeneralUtilities.format_xml_file_with_encoding(filepath, "utf-8")
 437
 1438    @staticmethod
 1439    @check_arguments
 1440    def format_xml_file_with_encoding(filepath: str, encoding: str) -> None:
 0441        with codecs.open(filepath, 'r', encoding=encoding) as file:
 0442            text = file.read()
 0443        text = parse(text).toprettyxml()
 0444        with codecs.open(filepath, 'w', encoding=encoding) as file:
 0445            file.write(text)
 446
 1447    @staticmethod
 1448    @check_arguments
 1449    def get_clusters_and_sectors_of_disk(diskpath: str) -> None:
 0450        sectorsPerCluster = ctypes.c_ulonglong(0)
 0451        bytesPerSector = ctypes.c_ulonglong(0)
 0452        rootPathName = ctypes.c_wchar_p(diskpath)
 0453        ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, ctypes.pointer(sectorsPerCluster), ctypes.pointer(bytesPe
 0454        return (sectorsPerCluster.value, bytesPerSector.value)
 455
 1456    @staticmethod
 1457    @check_arguments
 1458    def ensure_path_is_not_quoted(path: str) -> str:
 0459        if (path.startswith("\"") and path.endswith("\"")) or (path.startswith("'") and path.endswith("'")):
 0460            path = path[1:]
 0461            path = path[:-1]
 0462            return path
 463        else:
 0464            return path
 465
 1466    @staticmethod
 1467    @check_arguments
 1468    def get_missing_files(folderA: str, folderB: str) -> list:
 0469        folderA_length = len(folderA)
 0470        result = []
 0471        for fileA in GeneralUtilities.absolute_file_paths(folderA):
 0472            file = fileA[folderA_length:]
 0473            fileB = folderB + file
 0474            if not os.path.isfile(fileB):
 0475                result.append(fileB)
 0476        return result
 477
 1478    @staticmethod
 1479    @check_arguments
 1480    def to_pascal_case(s: str) -> str:
 1481        return ''.join(current.lower() if prev.isalnum() else current.upper() for prev, current in zip(' ' + s, s) if cu
 482
 1483    @staticmethod
 1484    @check_arguments
 1485    def find_between(s: str, start: str, end: str) -> str:
 1486        return s.split(start)[1].split(end)[0]
 487
 1488    @staticmethod
 1489    @check_arguments
 1490    def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None:
 1491        lines = [GeneralUtilities.strip_new_line_character(line) for line in lines]
 1492        content = os.linesep.join(lines)
 1493        GeneralUtilities.write_text_to_file(file, content, encoding)
 494
 1495    @staticmethod
 1496    @check_arguments
 1497    def write_text_to_file(file: str, content: str, encoding="utf-8") -> None:
 1498        GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding)))
 499
 1500    @staticmethod
 1501    @check_arguments
 1502    def write_binary_to_file(file: str, content: bytes) -> None:
 1503        with open(file, "wb") as file_object:
 1504            file_object.write(content)
 505
 1506    @staticmethod
 1507    @check_arguments
 1508    def read_lines_from_file(file: str, encoding="utf-8") -> list[str]:
 1509        return [GeneralUtilities.strip_new_line_character(line) for line in GeneralUtilities.read_text_from_file(file, e
 510
 1511    @staticmethod
 1512    @check_arguments
 1513    def read_text_from_file(file: str, encoding="utf-8") -> str:
 1514        return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding)
 515
 1516    @staticmethod
 1517    @check_arguments
 1518    def read_binary_from_file(file: str) -> bytes:
 1519        with open(file, "rb") as file_object:
 1520            return file_object.read()
 521
 1522    @staticmethod
 1523    @check_arguments
 1524    def timedelta_to_simple_string(delta: timedelta) -> str:
 0525        return (datetime(1970, 1, 1, 0, 0, 0) + delta).strftime('%H:%M:%S')
 526
 1527    @staticmethod
 1528    @check_arguments
 1529    def resolve_relative_path_from_current_working_directory(path: str) -> str:
 1530        return GeneralUtilities.resolve_relative_path(path, os.getcwd())
 531
 1532    @staticmethod
 1533    @check_arguments
 1534    def resolve_relative_path(path: str, base_path: str):
 1535        if (os.path.isabs(path)):
 1536            return path
 537        else:
 1538            return str(Path(os.path.join(base_path, path)).resolve())
 539
 1540    @staticmethod
 1541    @check_arguments
 1542    def get_metadata_for_file_for_clone_folder_structure(file: str) -> str:
 0543        size = os.path.getsize(file)
 0544        last_modified_timestamp = os.path.getmtime(file)
 0545        hash_value = GeneralUtilities.get_sha256_of_file(file)
 0546        last_access_timestamp = os.path.getatime(file)
 0547        return f'{{"size":"{size}","sha256":"{hash_value}","mtime":"{last_modified_timestamp}","atime":"{last_access_tim
 548
 1549    @staticmethod
 1550    @check_arguments
 1551    def clone_folder_structure(source: str, target: str, copy_only_metadata: bool):
 0552        source = GeneralUtilities.resolve_relative_path(source, os.getcwd())
 0553        target = GeneralUtilities.resolve_relative_path(target, os.getcwd())
 0554        length_of_source = len(source)
 0555        for source_file in GeneralUtilities.absolute_file_paths(source):
 0556            target_file = target+source_file[length_of_source:]
 0557            GeneralUtilities.ensure_directory_exists(os.path.dirname(target_file))
 0558            if copy_only_metadata:
 0559                with open(target_file, 'w', encoding='utf8') as f:
 0560                    f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file))
 561            else:
 0562                copyfile(source_file, target_file)
 563
 1564    @staticmethod
 1565    @check_arguments
 1566    def current_user_has_elevated_privileges() -> bool:
 0567        try:
 0568            return os.getuid() == 0
 0569        except AttributeError:
 0570            return ctypes.windll.shell32.IsUserAnAdmin() == 1
 571
 1572    @staticmethod
 1573    @check_arguments
 1574    def ensure_elevated_privileges() -> None:
 0575        if (not GeneralUtilities.current_user_has_elevated_privileges()):
 0576            raise ValueError("Not enough privileges.")
 577
 1578    @staticmethod
 1579    @check_arguments
 1580    def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=F
 0581        for file in GeneralUtilities.get_direct_files_of_folder(folder):
 0582            GeneralUtilities.replace_in_filename(file, replace_from, replace_to, replace_only_full_match)
 0583        for sub_folder in GeneralUtilities.get_direct_folders_of_folder(folder):
 0584            GeneralUtilities.rename_names_of_all_files_and_folders(sub_folder, replace_from, replace_to, replace_only_fu
 0585        GeneralUtilities.replace_in_foldername(folder, replace_from, replace_to, replace_only_full_match)
 586
 1587    @staticmethod
 1588    @check_arguments
 1589    def get_direct_files_of_folder(folder: str) -> list[str]:
 1590        result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))]
 1591        result = sorted(result, key=str.casefold)
 1592        return result
 593
 1594    @staticmethod
 1595    @check_arguments
 1596    def get_direct_folders_of_folder(folder: str) -> list[str]:
 1597        result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
 1598        result = sorted(result, key=str.casefold)
 1599        return result
 600
 1601    @staticmethod
 1602    @check_arguments
 1603    def get_all_files_of_folder(folder: str) -> list[str]:
 1604        result = list()
 1605        result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
 1606        for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
 1607            result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
 1608        result = sorted(result, key=str.casefold)
 1609        return result
 610
 1611    @staticmethod
 1612    @check_arguments
 1613    def get_all_folders_of_folder(folder: str) -> list[str]:
 1614        result = list()
 1615        subfolders = GeneralUtilities.get_direct_folders_of_folder(folder)
 1616        result.extend(subfolders)
 1617        for subfolder in subfolders:
 1618            result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
 1619        result = sorted(result, key=str.casefold)
 1620        return result
 621
 1622    @staticmethod
 1623    @check_arguments
 1624    def get_all_objects_of_folder(folder: str) -> list[str]:
 0625        return sorted(GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(fold
 626
 1627    @staticmethod
 1628    @check_arguments
 1629    def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
 0630        filename = Path(file).name
 0631        if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
 0632            folder_of_file = os.path.dirname(file)
 0633            os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
 634
 1635    @staticmethod
 1636    @check_arguments
 1637    def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
 0638        foldername = Path(folder).name
 0639        if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
 0640            folder_of_folder = os.path.dirname(folder)
 0641            os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
 642
 1643    @staticmethod
 1644    @check_arguments
 1645    def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool:
 0646        if replace_only_full_match:
 0647            return input_text == search_text
 648        else:
 0649            return search_text in input_text
 650
 1651    @staticmethod
 1652    @check_arguments
 1653    def str_none_safe(variable) -> str:
 1654        if variable is None:
 0655            return ''
 656        else:
 1657            return str(variable)
 658
 1659    @staticmethod
 1660    @check_arguments
 1661    def arguments_to_array(arguments_as_string: str) -> list[str]:
 1662        if arguments_as_string is None:
 0663            return []
 1664        if GeneralUtilities.string_has_content(arguments_as_string):
 1665            return arguments_as_string.split(" ")  # TODO this function should get improved to allow whitespaces in quot
 666        else:
 0667            return []
 668
 1669    @staticmethod
 1670    @check_arguments
 1671    def arguments_to_array_for_log(arguments_as_string: str) -> list[str]:
 0672        if arguments_as_string is None:
 0673            return None
 0674        return GeneralUtilities.arguments_to_array(arguments_as_string)
 675
 1676    @staticmethod
 1677    @check_arguments
 1678    def get_sha256_of_file(file: str) -> str:
 0679        sha256 = hashlib.sha256()
 0680        with open(file, "rb") as fileObject:
 0681            for chunk in iter(lambda: fileObject.read(4096), b""):
 0682                sha256.update(chunk)
 0683        return sha256.hexdigest()
 684
 1685    @staticmethod
 1686    @check_arguments
 1687    def remove_duplicates(input_list) -> list:
 1688        result = []
 1689        for item in input_list:
 1690            if not item in result:
 1691                result.append(item)
 1692        return result
 693
 1694    @staticmethod
 1695    @check_arguments
 1696    def print_stacktrace() -> None:
 0697        for line in traceback.format_stack():
 0698            GeneralUtilities.write_message_to_stdout(line.strip())
 699
 1700    @staticmethod
 1701    @check_arguments
 1702    def string_to_boolean(value: str) -> bool:
 0703        value = value.strip().lower()
 0704        if value in ('yes', 'y', 'true', 't', '1'):
 0705            return True
 0706        elif value in ('no', 'n', 'false', 'f', '0'):
 0707            return False
 708        else:
 0709            raise ValueError(f"Can not convert '{value}' to a boolean value")
 710
 1711    @staticmethod
 1712    @check_arguments
 1713    def file_is_empty(file: str) -> bool:
 0714        return os.stat(file).st_size == 0
 715
 1716    @staticmethod
 1717    @check_arguments
 1718    def folder_is_empty(folder: str) -> bool:
 0719        return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders
 720
 1721    @staticmethod
 1722    @check_arguments
 1723    def get_time_based_logfile_by_folder(folder: str, name: str = "Log", in_utc: bool = False) -> str:
 0724        return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUti
 725
 1726    @staticmethod
 1727    @check_arguments
 1728    def get_time_based_logfilename(name: str = "Log", in_utc: bool = False) -> str:
 0729        if (in_utc):
 0730            d = datetime.utcnow()
 731        else:
 0732            d = datetime.now()
 0733        return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}"
 734
 1735    @staticmethod
 1736    @check_arguments
 1737    def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str:
 1738        return payload.decode(encoding, errors="ignore")
 739
 1740    @staticmethod
 1741    @check_arguments
 1742    def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes:
 0743        return payload.encode(encoding, errors="ignore")
 744
 1745    @staticmethod
 1746    @check_arguments
 1747    def contains_line(lines, regex: str) -> bool:
 0748        for line in lines:
 0749            if (re.match(regex, line)):
 0750                return True
 0751        return False
 752
 1753    @staticmethod
 1754    @check_arguments
 1755    def read_csv_file(file: str, ignore_first_line: bool = False, treat_number_sign_at_begin_of_line_as_comment: bool = 
 0756        lines = GeneralUtilities.read_lines_from_file(file, encoding)
 757
 0758        if ignore_first_line:
 0759            lines = lines[1:]
 0760        result = list()
 761        line: str
 0762        for line_loopvariable in lines:
 0763            use_line = True
 0764            line = line_loopvariable
 765
 0766            if trim_values:
 0767                line = line.strip()
 0768            if ignore_empty_lines:
 0769                if not GeneralUtilities.string_has_content(line):
 0770                    use_line = False
 771
 0772            if treat_number_sign_at_begin_of_line_as_comment:
 0773                if line.startswith("#"):
 0774                    use_line = False
 775
 0776            if use_line:
 0777                if separator_character in line:
 0778                    raw_values_of_line = GeneralUtilities.to_list(line, separator_character)
 779                else:
 0780                    raw_values_of_line = [line]
 0781                if trim_values:
 0782                    raw_values_of_line = [value.strip() for value in raw_values_of_line]
 0783                values_of_line = []
 0784                for raw_value_of_line in raw_values_of_line:
 0785                    value_of_line = raw_value_of_line
 0786                    if values_are_surrounded_by_quotes:
 0787                        value_of_line = value_of_line[1:]
 0788                        value_of_line = value_of_line[:-1]
 0789                        value_of_line = value_of_line.replace('""', '"')
 0790                    values_of_line.append(value_of_line)
 0791                result.extend([values_of_line])
 0792        return result
 793
 1794    @staticmethod
 1795    @check_arguments
 1796    def epew_is_available() -> bool:
 0797        return GeneralUtilities.tool_is_available("epew")
 798
 1799    @staticmethod
 1800    @check_arguments
 1801    def tool_is_available(toolname: str) -> bool:
 0802        try:
 0803            return shutil.which(toolname) is not None
 0804        except:
 0805            return False
 806
 1807    @staticmethod
 1808    @check_arguments
 1809    @deprecated
 1810    def absolute_file_paths(directory: str) -> list[str]:
 0811        return GeneralUtilities.get_all_files_of_folder(directory)
 812
 1813    @staticmethod
 1814    @check_arguments
 1815    def to_list(list_as_string: str, separator: str = ",") -> list[str]:
 1816        result = list()
 1817        if list_as_string is not None:
 1818            list_as_string = list_as_string.strip()
 1819            if list_as_string == "":
 1820                pass
 1821            elif separator in list_as_string:
 1822                for item in list_as_string.split(separator):
 1823                    result.append(item.strip())
 824            else:
 1825                result.append(list_as_string)
 1826        return result
 827
 1828    @staticmethod
 1829    @check_arguments
 1830    def get_next_square_number(number: int) -> int:
 1831        GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative number
 1832        if number == 0:
 1833            return 1
 1834        root = 0
 1835        square = 0
 1836        while square < number:
 1837            root = root+1
 1838            square = root*root
 1839        return root*root
 840
 1841    @staticmethod
 1842    @check_arguments
 1843    def generate_password(length: int = 16, alphabet: str = None) -> None:
 0844        if alphabet is None:
 0845            alphabet = strin.ascii_letters + strin.digits+"_"
 0846        return ''.join(secrets.choice(alphabet) for i in range(length))
 847
 1848    @staticmethod
 1849    @check_arguments
 1850    def assert_condition(condition: bool, information: str = None) -> None:
 851        """Throws an exception if the condition is false."""
 1852        if (not condition):
 0853            if information is None:
 0854                information = "Internal assertion error."
 0855            raise ValueError("Condition failed. "+information)
 856
 1857    @staticmethod
 1858    def current_system_is_windows():
 0859        return platform.system() == 'Windows'
 860
 1861    @staticmethod
 1862    def current_system_is_linux():
 0863        return platform.system() == 'Linux'
 864
 1865    @staticmethod
 1866    @check_arguments
 1867    def get_certificate_expiry_date(certificate_file: str) -> datetime:
 0868        with open(certificate_file, encoding="utf-8") as certificate_file_content:
 0869            cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read())
 0870            date_as_bytes = cert.get_notAfter()
 0871            date_as_string = date_as_bytes.decode("utf-8")
 0872            result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ')
 0873            return result
 874
 1875    @staticmethod
 1876    @check_arguments
 1877    def certificate_is_expired(certificate_file: str) -> bool:
 0878        return GeneralUtilities.get_certificate_expiry_date(certificate_file) < datetime.now()
 879
 1880    @staticmethod
 1881    @check_arguments
 1882    def internet_connection_is_available() -> bool:
 883        # TODO add more hosts to check to return true if at least one is available
 0884        try:
 0885            with urllib.request.urlopen("https://www.google.com") as url_result:
 0886                return (url_result.code // 100) == 2
 0887        except:
 0888            pass
 0889        return False
 890
 1891    @staticmethod
 1892    @check_arguments
 1893    def replace_variable_in_string(input_string: str, variable_name: str, variable_value: str) -> None:
 0894        return input_string.replace(f"__[{variable_name}]__", variable_value)
 895
 1896    @staticmethod
 1897    @check_arguments
 1898    def input(prompt: str, print_result: bool) -> str:  # This function is a workaround for usescases like python script
 0899        GeneralUtilities.write_message_to_stdout(prompt)
 0900        result: str = input()
 0901        if print_result:
 0902            GeneralUtilities.write_message_to_stdout(f"Result: {result}")
 0903        return result
 904
 1905    @staticmethod
 1906    @check_arguments
 1907    def run_program_simple(program: str, arguments: list[str], cwd: str = None) -> tuple[int, str, str]:
 0908        if cwd is None:
 0909            cwd = os.getcwd()
 0910        cmd = [program]+arguments
 0911        with subprocess.Popen(cmd, cwd=cwd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as process:
 0912            stdout, stderr = process.communicate()
 0913            exit_code = process.wait()
 0914            return (exit_code, stdout, stderr)
 915
 1916    @staticmethod
 1917    @check_arguments
 1918    def assert_file_exists(file: str) -> str:
 0919        GeneralUtilities.assert_condition(os.path.isfile(file), f"File '{file}' does not exist.")
 920
 1921    @staticmethod
 1922    @check_arguments
 1923    def assert_folder_exists(folder: str) -> str:
 0924        GeneralUtilities.assert_condition(os.path.isdir(folder), f"Folder '{folder}' does not exist.")
 925
 1926    @staticmethod
 1927    @check_arguments
 1928    def retry_action(action, amount_of_attempts: int) -> None:
 0929        amount_of_fails = 0
 0930        enabled = True
 0931        while enabled:
 0932            try:
 0933                result=action()
 0934                return result
 0935            except Exception:
 0936                amount_of_fails = amount_of_fails+1
 0937                GeneralUtilities.assert_condition(not (amount_of_attempts < amount_of_fails))
 0938                if amount_of_fails == amount_of_attempts:
 0939                    raise
 0940        return None
 941
 1942    @staticmethod
 1943    @check_arguments
 1944    def int_to_string(number: int, leading_zeroplaces: int, trailing_zeroplaces: int) -> str:
 1945        return GeneralUtilities.float_to_string(float(number), leading_zeroplaces, trailing_zeroplaces)
 946
 1947    @staticmethod
 1948    @check_arguments
 1949    def float_to_string(number: float, leading_zeroplaces: int, trailing_zeroplaces: int) -> str:
 1950        plain_str = str(number)
 1951        GeneralUtilities.assert_condition("." in plain_str)
 1952        splitted: list[str] = plain_str.split(".")
 1953        return splitted[0].zfill(leading_zeroplaces)+"."+splitted[1].ljust(trailing_zeroplaces, '0')

Methods/Properties