ScriptCollection 3.3.23__py3-none-any.whl → 4.0.78__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ScriptCollection/AnionBuildPlatform.py +206 -0
- ScriptCollection/{UpdateCertificates.py → CertificateUpdater.py} +149 -128
- ScriptCollection/Executables.py +868 -292
- ScriptCollection/GeneralUtilities.py +609 -107
- ScriptCollection/ImageUpdater.py +648 -0
- ScriptCollection/ProcessesRunner.py +41 -0
- ScriptCollection/ProgramRunnerBase.py +47 -42
- ScriptCollection/ProgramRunnerMock.py +2 -0
- ScriptCollection/ProgramRunnerPopen.py +57 -50
- ScriptCollection/ProgramRunnerSudo.py +108 -0
- ScriptCollection/SCLog.py +115 -0
- ScriptCollection/ScriptCollectionCore.py +2541 -1383
- ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py +95 -0
- ScriptCollection/TFCPS/Docker/__init__.py +0 -0
- ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationBase.py +8 -0
- ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationGenerate.py +6 -0
- ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationNoGenerate.py +7 -0
- ScriptCollection/TFCPS/DotNet/TFCPS_CodeUnitSpecific_DotNet.py +485 -0
- ScriptCollection/TFCPS/DotNet/__init__.py +0 -0
- ScriptCollection/TFCPS/Flutter/TFCPS_CodeUnitSpecific_Flutter.py +130 -0
- ScriptCollection/TFCPS/Flutter/__init__.py +0 -0
- ScriptCollection/TFCPS/Go/TFCPS_CodeUnitSpecific_Go.py +74 -0
- ScriptCollection/TFCPS/Go/__init__.py +0 -0
- ScriptCollection/TFCPS/NodeJS/TFCPS_CodeUnitSpecific_NodeJS.py +131 -0
- ScriptCollection/TFCPS/NodeJS/__init__.py +0 -0
- ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py +227 -0
- ScriptCollection/TFCPS/Python/__init__.py +0 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnitSpecific_Base.py +418 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnit_BuildCodeUnit.py +128 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnit_BuildCodeUnits.py +136 -0
- ScriptCollection/TFCPS/TFCPS_CreateRelease.py +95 -0
- ScriptCollection/TFCPS/TFCPS_Generic.py +43 -0
- ScriptCollection/TFCPS/TFCPS_MergeToMain.py +122 -0
- ScriptCollection/TFCPS/TFCPS_MergeToStable.py +350 -0
- ScriptCollection/TFCPS/TFCPS_PreBuildCodeunitsScript.py +47 -0
- ScriptCollection/TFCPS/TFCPS_Tools_General.py +1356 -0
- ScriptCollection/TFCPS/__init__.py +0 -0
- {ScriptCollection-3.3.23.dist-info → scriptcollection-4.0.78.dist-info}/METADATA +26 -21
- scriptcollection-4.0.78.dist-info/RECORD +43 -0
- {ScriptCollection-3.3.23.dist-info → scriptcollection-4.0.78.dist-info}/WHEEL +1 -1
- scriptcollection-4.0.78.dist-info/entry_points.txt +64 -0
- ScriptCollection/Hardening.py +0 -59
- ScriptCollection/ProgramRunnerEpew.py +0 -122
- ScriptCollection/TasksForCommonProjectStructure.py +0 -1170
- ScriptCollection-3.3.23.dist-info/RECORD +0 -15
- ScriptCollection-3.3.23.dist-info/entry_points.txt +0 -24
- {ScriptCollection-3.3.23.dist-info → scriptcollection-4.0.78.dist-info}/top_level.txt +0 -0
|
@@ -1,30 +1,68 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
from os import listdir
|
|
4
|
+
from os.path import isfile, join, isdir
|
|
1
5
|
import codecs
|
|
6
|
+
import platform
|
|
2
7
|
import inspect
|
|
3
8
|
import ctypes
|
|
4
9
|
import hashlib
|
|
5
|
-
import
|
|
6
|
-
import os
|
|
10
|
+
import subprocess
|
|
7
11
|
import shutil
|
|
12
|
+
import time
|
|
13
|
+
import urllib
|
|
8
14
|
import stat
|
|
15
|
+
import fnmatch
|
|
16
|
+
import secrets
|
|
17
|
+
import string as strin
|
|
9
18
|
import sys
|
|
19
|
+
from enum import Enum
|
|
10
20
|
import traceback
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from os.path import isfile, join, isdir
|
|
21
|
+
import warnings
|
|
22
|
+
import functools
|
|
14
23
|
from pathlib import Path
|
|
15
|
-
from
|
|
24
|
+
from datetime import datetime, timedelta, date, timezone
|
|
16
25
|
import typing
|
|
26
|
+
from packaging.version import Version
|
|
27
|
+
import psutil
|
|
17
28
|
from defusedxml.minidom import parse
|
|
29
|
+
from OpenSSL import crypto
|
|
30
|
+
|
|
31
|
+
class VersionEcholon(Enum):
|
|
32
|
+
LatestPatch = 0
|
|
33
|
+
LatestPatchOrLatestMinor = 1
|
|
34
|
+
LatestPatchOrLatestMinorOrNextMajor = 2
|
|
35
|
+
LatestVersion = 3
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Dependency:
|
|
39
|
+
dependencyname:str
|
|
40
|
+
current_version:str
|
|
18
41
|
|
|
42
|
+
def __init__(self,dependencyname:str,current_version:str):
|
|
43
|
+
self.dependencyname=dependencyname
|
|
44
|
+
self.current_version=current_version
|
|
19
45
|
|
|
20
46
|
class GeneralUtilities:
|
|
21
47
|
|
|
22
|
-
|
|
48
|
+
__datetime_format_with_offset: str = "%Y-%m-%d %H:%M:%S %z"
|
|
49
|
+
__datetime_format: str = "%Y-%m-%dT%H:%M:%S"
|
|
50
|
+
__date_format: str = "%Y-%m-%d"
|
|
51
|
+
|
|
52
|
+
empty_string: str = ""
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def get_modest_dark_url() -> str:
|
|
56
|
+
return "https://aniondev.github.io/CDN/ScriptCollectionDesigns/ModestDark/Style.css"
|
|
23
57
|
|
|
24
58
|
@staticmethod
|
|
25
59
|
def is_generic(t: typing.Type):
|
|
26
60
|
return hasattr(t, "__origin__")
|
|
27
61
|
|
|
62
|
+
@staticmethod
|
|
63
|
+
def is_debugger_attached():
|
|
64
|
+
return sys.gettrace() is not None
|
|
65
|
+
|
|
28
66
|
@staticmethod
|
|
29
67
|
def check_arguments(function):
|
|
30
68
|
def __check_function(*args, **named_args):
|
|
@@ -39,19 +77,27 @@ class GeneralUtilities:
|
|
|
39
77
|
# Check type of arguments if the type is a generic type seems to be impossible.
|
|
40
78
|
if not GeneralUtilities.is_generic(function.__annotations__[parameters[index]]):
|
|
41
79
|
if not isinstance(argument, function.__annotations__[parameters[index]]):
|
|
42
|
-
raise TypeError(f"Argument with index {index} for function {function.__name__} ('{str(argument)}') is not of type "
|
|
43
|
-
f"{ function.__annotations__[parameters[index]]} but has type "+str(type(argument)))
|
|
80
|
+
raise TypeError(f"Argument with index {index} for function {function.__name__} ('{str(argument)}') is not of type {function.__annotations__[parameters[index]]} but has type "+str(type(argument)))
|
|
44
81
|
for index, named_argument in enumerate(named_args):
|
|
45
82
|
if named_args[named_argument] is not None:
|
|
46
83
|
if parameters[index] in function.__annotations__:
|
|
47
84
|
if not GeneralUtilities.is_generic(function.__annotations__.get(named_argument)):
|
|
48
85
|
if not isinstance(named_args[named_argument], function.__annotations__.get(named_argument)):
|
|
49
|
-
raise TypeError(f"Argument with name {named_argument} for function {function.__name__} ('{str(named_args[named_argument])}') "
|
|
50
|
-
f"is not of type { function.__annotations__.get(named_argument)}")
|
|
86
|
+
raise TypeError(f"Argument with name {named_argument} for function {function.__name__} ('{str(named_args[named_argument])}') is not of type {function.__annotations__.get(named_argument)}")
|
|
51
87
|
return function(*args, **named_args)
|
|
52
88
|
__check_function.__doc__ = function.__doc__
|
|
53
89
|
return __check_function
|
|
54
90
|
|
|
91
|
+
@staticmethod
|
|
92
|
+
def deprecated(func):
|
|
93
|
+
@functools.wraps(func)
|
|
94
|
+
def new_func(*args, **kwargs):
|
|
95
|
+
warnings.simplefilter('always', DeprecationWarning)
|
|
96
|
+
warnings.warn(f"Call to deprecated function {func.__name__}", category=DeprecationWarning, stacklevel=2)
|
|
97
|
+
warnings.simplefilter('default', DeprecationWarning)
|
|
98
|
+
return func(*args, **kwargs)
|
|
99
|
+
return new_func
|
|
100
|
+
|
|
55
101
|
@staticmethod
|
|
56
102
|
@check_arguments
|
|
57
103
|
def args_array_surround_with_quotes_if_required(arguments: list[str]) -> list[str]:
|
|
@@ -66,10 +112,10 @@ class GeneralUtilities:
|
|
|
66
112
|
@staticmethod
|
|
67
113
|
@check_arguments
|
|
68
114
|
def string_to_lines(string: str, add_empty_lines: bool = True, adapt_lines: bool = True) -> list[str]:
|
|
69
|
-
result = list()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if("\n" in string):
|
|
115
|
+
result : list[str] = list[str]()
|
|
116
|
+
lines : list[str] = list[str]()
|
|
117
|
+
if (string is not None):
|
|
118
|
+
if ("\n" in string):
|
|
73
119
|
lines = string.split("\n")
|
|
74
120
|
else:
|
|
75
121
|
lines.append(string)
|
|
@@ -85,55 +131,161 @@ class GeneralUtilities:
|
|
|
85
131
|
result.append(line)
|
|
86
132
|
return result
|
|
87
133
|
|
|
134
|
+
@staticmethod
|
|
135
|
+
@check_arguments
|
|
136
|
+
def string_to_datetime(value: str) -> datetime:
|
|
137
|
+
if "." in value:
|
|
138
|
+
value = value.split(".")[0]
|
|
139
|
+
return datetime.strptime(value, GeneralUtilities.__datetime_format) # value ="2022-10-06T19:26:01" for example
|
|
140
|
+
|
|
88
141
|
@staticmethod
|
|
89
142
|
@check_arguments
|
|
90
143
|
def datetime_to_string(value: datetime) -> str:
|
|
91
|
-
|
|
144
|
+
value = datetime(year=value.year, month=value.month, day=value.day, hour=value.hour, minute=value.minute, second=value.second)
|
|
145
|
+
return value.strftime(GeneralUtilities.__datetime_format) # returns "2022-10-06T19:26:01" for example
|
|
92
146
|
|
|
93
147
|
@staticmethod
|
|
94
148
|
@check_arguments
|
|
95
|
-
def
|
|
96
|
-
return
|
|
149
|
+
def datetime_to_string_with_timezone(value: datetime) -> str:
|
|
150
|
+
return value.strftime(GeneralUtilities.__datetime_format_with_offset) # returns "2025-08-21 15:30:00 +0200" for example
|
|
97
151
|
|
|
98
152
|
@staticmethod
|
|
99
153
|
@check_arguments
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
154
|
+
def string_to_date(value: str) -> date:
|
|
155
|
+
splitted = value.split("-")
|
|
156
|
+
return date(int(splitted[0]), int(splitted[1]), int(splitted[2])) # value ="2022-10-06" for example
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
@check_arguments
|
|
160
|
+
def date_to_string(value: date) -> str:
|
|
161
|
+
return value.strftime(GeneralUtilities.__date_format) # returns "2022-10-06" for example
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
@check_arguments
|
|
165
|
+
def copy_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False, ignored_glob_patterms: list[str] = None) -> None:
|
|
166
|
+
GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, False, ignored_glob_patterms)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
@check_arguments
|
|
170
|
+
def move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False, ignored_glob_patterms: list[str] = None) -> None:
|
|
171
|
+
GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, True, ignored_glob_patterms)
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
@check_arguments
|
|
175
|
+
def merge_dependency_lists(versions:list[list[Dependency]]) -> dict[str,set[str]]:
|
|
176
|
+
result:dict[str,set[str]]=dict[str,set[str]]()
|
|
177
|
+
for dlist in versions:
|
|
178
|
+
for ditem in dlist:
|
|
179
|
+
if not ditem.dependencyname in result:
|
|
180
|
+
result[ditem.dependencyname]=set[str]()
|
|
181
|
+
result[ditem.dependencyname].add(ditem.current_version)
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
@check_arguments
|
|
186
|
+
def choose_version(available_versions:list[str],current_version:str,echolon:VersionEcholon) -> str:
|
|
187
|
+
match echolon:
|
|
188
|
+
case VersionEcholon.LatestPatch:
|
|
189
|
+
return GeneralUtilities.get_latest_version(GeneralUtilities.filter_versions_by_prefix(available_versions,f"{GeneralUtilities.get_major_part_of_version(current_version)}.{GeneralUtilities.get_minor_part_of_version(current_version)}."))
|
|
190
|
+
case VersionEcholon.LatestPatchOrLatestMinor:
|
|
191
|
+
return GeneralUtilities.get_latest_version(GeneralUtilities.filter_versions_by_prefix(available_versions,f"{GeneralUtilities.get_major_part_of_version(current_version)}."))
|
|
192
|
+
case VersionEcholon.LatestPatchOrLatestMinorOrNextMajor:
|
|
193
|
+
raise ValueError("not implemented")#TODO
|
|
194
|
+
case VersionEcholon.LatestVersion:
|
|
195
|
+
return GeneralUtilities.get_latest_version(available_versions)
|
|
196
|
+
case _:
|
|
197
|
+
raise ValueError("Unknown echolon-value: "+str(echolon))
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
@check_arguments
|
|
201
|
+
def get_latest_version(versions:list[str]) -> str:
|
|
202
|
+
GeneralUtilities.assert_condition(0<len(versions),"Version-list can not be empty.")
|
|
203
|
+
latest = max(versions, key=Version)
|
|
204
|
+
return latest
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
@check_arguments
|
|
208
|
+
def filter_versions_by_prefix(versions:list[str],prefix:str) -> list[str]:
|
|
209
|
+
return [v for v in versions if v.startswith(prefix)]
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
@check_arguments
|
|
213
|
+
def get_major_part_of_version(version:str) -> int:
|
|
214
|
+
return GeneralUtilities.get_version_parts(version)[0]
|
|
215
|
+
|
|
216
|
+
@staticmethod
|
|
217
|
+
@check_arguments
|
|
218
|
+
def get_minor_part_of_version(version:str) -> int:
|
|
219
|
+
return GeneralUtilities.get_version_parts(version)[1]
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
@check_arguments
|
|
223
|
+
def get_patch_part_of_version(version:str) -> int:
|
|
224
|
+
return GeneralUtilities.get_version_parts(version)[2]
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
@check_arguments
|
|
228
|
+
def get_version_parts(version:str) -> tuple[int,int,int]:
|
|
229
|
+
match = re.match(r"^(\d+).(\d+).(\d+)$", version)
|
|
230
|
+
GeneralUtilities.assert_condition(match is not None,f"string \"{version}\" is not a valid version.")
|
|
231
|
+
return (int(match.group(1)),int(match.group(2)),int(match.group(3)))
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
@check_arguments
|
|
235
|
+
def is_ignored_by_glob_pattern(source_directory:str,path:str, ignored_glob_patterms: list[str]) -> bool:
|
|
236
|
+
source_directory=source_directory.replace("\\","/")
|
|
237
|
+
path=path.replace("\\","/")
|
|
238
|
+
GeneralUtilities.assert_condition(path.startswith(source_directory), f"Path '{path}' is not located in source directory '{source_directory}'.")
|
|
239
|
+
if ignored_glob_patterms is None:
|
|
240
|
+
return False
|
|
241
|
+
relative_path = os.path.relpath(path, source_directory)
|
|
242
|
+
for pattern in ignored_glob_patterms:
|
|
243
|
+
if fnmatch.filter([relative_path], pattern):
|
|
244
|
+
return True
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
@check_arguments
|
|
250
|
+
def __copy_or_move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files:bool, remove_source: bool,ignored_glob_patterms: list[str] = None) -> None:
|
|
251
|
+
srcDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(source_directory)
|
|
252
|
+
dstDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(target_directory)
|
|
253
|
+
if (os.path.isdir(source_directory)):
|
|
254
|
+
GeneralUtilities.ensure_directory_exists(target_directory)
|
|
105
255
|
for file in GeneralUtilities.get_direct_files_of_folder(srcDirFull):
|
|
106
256
|
filename = os.path.basename(file)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if
|
|
110
|
-
|
|
257
|
+
if not GeneralUtilities.is_ignored_by_glob_pattern(source_directory,file, ignored_glob_patterms):
|
|
258
|
+
targetfile = os.path.join(dstDirFull, filename)
|
|
259
|
+
if (os.path.isfile(targetfile)):
|
|
260
|
+
if overwrite_existing_files:
|
|
261
|
+
GeneralUtilities.ensure_file_does_not_exist(targetfile)
|
|
262
|
+
else:
|
|
263
|
+
raise ValueError(f"Targetfile '{targetfile}' does already exist.")
|
|
264
|
+
if remove_source:
|
|
265
|
+
shutil.move(file, dstDirFull)
|
|
111
266
|
else:
|
|
112
|
-
|
|
113
|
-
shutil.move(file, dstDirFull)
|
|
267
|
+
shutil.copy(file, dstDirFull)
|
|
114
268
|
for sub_folder in GeneralUtilities.get_direct_folders_of_folder(srcDirFull):
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
269
|
+
if not GeneralUtilities.is_ignored_by_glob_pattern(source_directory,sub_folder, ignored_glob_patterms):
|
|
270
|
+
foldername = os.path.basename(sub_folder)
|
|
271
|
+
sub_target = os.path.join(dstDirFull, foldername)
|
|
272
|
+
GeneralUtilities.__copy_or_move_content_of_folder(sub_folder, sub_target, overwrite_existing_files, remove_source,ignored_glob_patterms)
|
|
273
|
+
if remove_source:
|
|
274
|
+
GeneralUtilities.ensure_directory_does_not_exist(sub_folder)
|
|
119
275
|
else:
|
|
120
|
-
raise ValueError(f"Folder '{
|
|
276
|
+
raise ValueError(f"Folder '{source_directory}' does not exist")
|
|
121
277
|
|
|
122
278
|
@staticmethod
|
|
123
279
|
@check_arguments
|
|
124
280
|
def replace_regex_each_line_of_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8", verbose: bool = False) -> None:
|
|
125
|
-
"""This function iterates over each line in the file and replaces it by the line which applied regex.
|
|
126
|
-
Note: The lines will be taken from open(...).readlines(). So the lines may contain '\\n' or '\\r\\n' for example."""
|
|
127
281
|
if verbose:
|
|
128
282
|
GeneralUtilities.write_message_to_stdout(f"Replace '{replace_from_regex}' to '{replace_to_regex}' in '{file}'")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
with open(file, encoding=encoding, mode="w") as f:
|
|
136
|
-
f.writelines(replaced_lines)
|
|
283
|
+
lines=GeneralUtilities.read_lines_from_file(file,encoding)
|
|
284
|
+
replaced_lines = []
|
|
285
|
+
for line in lines:
|
|
286
|
+
replaced_line = re.sub(replace_from_regex, replace_to_regex, line)
|
|
287
|
+
replaced_lines.append(replaced_line)
|
|
288
|
+
GeneralUtilities.write_lines_to_file(file,replaced_lines,encoding)
|
|
137
289
|
|
|
138
290
|
@staticmethod
|
|
139
291
|
@check_arguments
|
|
@@ -147,6 +299,7 @@ class GeneralUtilities:
|
|
|
147
299
|
@staticmethod
|
|
148
300
|
@check_arguments
|
|
149
301
|
def replace_xmltag_in_file(file: str, tag: str, new_value: str, encoding="utf-8") -> None:
|
|
302
|
+
GeneralUtilities.assert_condition(tag.isalnum(tag), f"Invalid tag: \"{tag}\"")
|
|
150
303
|
GeneralUtilities.replace_regex_in_file(file, f"<{tag}>.*</{tag}>", f"<{tag}>{new_value}</{tag}>", encoding)
|
|
151
304
|
|
|
152
305
|
@staticmethod
|
|
@@ -165,7 +318,7 @@ class GeneralUtilities:
|
|
|
165
318
|
for key, value in replacements.items():
|
|
166
319
|
previousValue = text
|
|
167
320
|
text = text.replace(f"__{key}__", value)
|
|
168
|
-
if(not text == previousValue):
|
|
321
|
+
if (not text == previousValue):
|
|
169
322
|
changed = True
|
|
170
323
|
return text
|
|
171
324
|
|
|
@@ -176,17 +329,78 @@ class GeneralUtilities:
|
|
|
176
329
|
text = GeneralUtilities.replace_underscores_in_text(text, replacements)
|
|
177
330
|
GeneralUtilities.write_text_to_file(file, text, encoding)
|
|
178
331
|
|
|
332
|
+
@staticmethod
|
|
333
|
+
@check_arguments
|
|
334
|
+
def print_text(text: str, print_to_stdout: bool = True):
|
|
335
|
+
stream: object = sys.stdout if print_to_stdout else sys.stderr
|
|
336
|
+
GeneralUtilities.__print_text_to_console(text, stream)
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
@check_arguments
|
|
340
|
+
def print_text_in_green(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
341
|
+
GeneralUtilities.print_text_in_color(text, 32, print_to_stdout, print_as_color)
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
@check_arguments
|
|
345
|
+
def print_text_in_yellow(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
346
|
+
GeneralUtilities.print_text_in_color(text, 33, print_to_stdout, print_as_color)
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
@check_arguments
|
|
350
|
+
def print_text_in_red(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
351
|
+
GeneralUtilities.print_text_in_color(text, 31, print_to_stdout, print_as_color)
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
@check_arguments
|
|
355
|
+
def print_text_in_cyan(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
356
|
+
GeneralUtilities.print_text_in_color(text, 36, print_to_stdout, print_as_color)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
@check_arguments
|
|
360
|
+
def print_text_in_color(text: str, colorcode: int, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
361
|
+
stream: object = sys.stdout if print_to_stdout else sys.stderr
|
|
362
|
+
if print_as_color:
|
|
363
|
+
text = f"\033[{colorcode}m{text}\033[0m"
|
|
364
|
+
GeneralUtilities.__print_text_to_console(text, stream)
|
|
365
|
+
|
|
366
|
+
@staticmethod
|
|
367
|
+
@check_arguments
|
|
368
|
+
def __print_text_to_console(text: str, stream: object):
|
|
369
|
+
stream.write(text)
|
|
370
|
+
stream.flush()
|
|
371
|
+
|
|
372
|
+
@staticmethod
|
|
373
|
+
@check_arguments
|
|
374
|
+
def reconfigure_standrd_input_and_outputs():
|
|
375
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
376
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
377
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
378
|
+
|
|
379
|
+
@staticmethod
|
|
380
|
+
@check_arguments
|
|
381
|
+
def write_message_to_stdout_advanced(message: str, add_empty_lines: bool = True, adapt_lines: bool = True, append_linebreak: bool = True):
|
|
382
|
+
new_line_character: str = "\n" if append_linebreak else GeneralUtilities.empty_string
|
|
383
|
+
for line in GeneralUtilities.string_to_lines(message, add_empty_lines, adapt_lines):
|
|
384
|
+
sys.stdout.write(GeneralUtilities.str_none_safe(line)+new_line_character)
|
|
385
|
+
sys.stdout.flush()
|
|
386
|
+
|
|
179
387
|
@staticmethod
|
|
180
388
|
@check_arguments
|
|
181
389
|
def write_message_to_stdout(message: str):
|
|
182
|
-
|
|
183
|
-
|
|
390
|
+
GeneralUtilities.write_message_to_stdout_advanced(message, True, True, True)
|
|
391
|
+
|
|
392
|
+
@staticmethod
|
|
393
|
+
@check_arguments
|
|
394
|
+
def write_message_to_stderr_advanced(message: str, add_empty_lines: bool = True, adapt_lines: bool = True, append_linebreak: bool = True):
|
|
395
|
+
new_line_character: str = "\n" if append_linebreak else GeneralUtilities.empty_string
|
|
396
|
+
for line in GeneralUtilities.string_to_lines(message, add_empty_lines, adapt_lines):
|
|
397
|
+
sys.stderr.write(GeneralUtilities.str_none_safe(line)+new_line_character)
|
|
398
|
+
sys.stderr.flush()
|
|
184
399
|
|
|
185
400
|
@staticmethod
|
|
186
401
|
@check_arguments
|
|
187
402
|
def write_message_to_stderr(message: str):
|
|
188
|
-
|
|
189
|
-
sys.stderr.flush()
|
|
403
|
+
GeneralUtilities.write_message_to_stderr_advanced(message, True, True, True)
|
|
190
404
|
|
|
191
405
|
@staticmethod
|
|
192
406
|
@check_arguments
|
|
@@ -194,7 +408,7 @@ class GeneralUtilities:
|
|
|
194
408
|
if GeneralUtilities.string_has_content(os_error.filename2):
|
|
195
409
|
secondpath = f" {os_error.filename2}"
|
|
196
410
|
else:
|
|
197
|
-
secondpath =
|
|
411
|
+
secondpath = GeneralUtilities.empty_string
|
|
198
412
|
return f"Related path(s): {os_error.filename}{secondpath}"
|
|
199
413
|
|
|
200
414
|
@staticmethod
|
|
@@ -208,7 +422,7 @@ class GeneralUtilities:
|
|
|
208
422
|
GeneralUtilities.write_message_to_stderr("Exception(")
|
|
209
423
|
GeneralUtilities.write_message_to_stderr("Type: " + str(type(exception)))
|
|
210
424
|
GeneralUtilities.write_message_to_stderr("Message: " + str(exception))
|
|
211
|
-
if
|
|
425
|
+
if extra_message is not None:
|
|
212
426
|
GeneralUtilities.write_message_to_stderr("Extra-message: " + str(extra_message))
|
|
213
427
|
if isinstance(exception, OSError):
|
|
214
428
|
GeneralUtilities.write_message_to_stderr(GeneralUtilities.get_advanced_errormessage_for_os_error(exception))
|
|
@@ -222,17 +436,30 @@ class GeneralUtilities:
|
|
|
222
436
|
if string is None:
|
|
223
437
|
return False
|
|
224
438
|
else:
|
|
225
|
-
return len(string) > 0
|
|
439
|
+
return len(string.strip()) > 0
|
|
226
440
|
|
|
227
441
|
@staticmethod
|
|
228
442
|
@check_arguments
|
|
229
|
-
def datetime_to_string_for_logfile_name(datetime_object: datetime) -> str:
|
|
230
|
-
|
|
443
|
+
def datetime_to_string_for_logfile_name(datetime_object: datetime, add_timezone_info_to_log: bool = True) -> str:
|
|
444
|
+
base_pattern: str = "%Y-%m-%d_%H-%M-%S"
|
|
445
|
+
if add_timezone_info_to_log:
|
|
446
|
+
return datetime_object.strftime(f'{base_pattern}_%z')
|
|
447
|
+
else:
|
|
448
|
+
return datetime_object.strftime(base_pattern)
|
|
231
449
|
|
|
232
450
|
@staticmethod
|
|
233
451
|
@check_arguments
|
|
234
|
-
def datetime_to_string_for_logfile_entry(datetime_object: datetime) -> str:
|
|
235
|
-
|
|
452
|
+
def datetime_to_string_for_logfile_entry(datetime_object: datetime, add_milliseconds: bool = False) -> str:
|
|
453
|
+
if datetime_object.tzinfo is None:
|
|
454
|
+
datetime_object = datetime_object.replace(tzinfo=timezone.utc) # assume utc when no timezone is given
|
|
455
|
+
pattern: str = None
|
|
456
|
+
if add_milliseconds:
|
|
457
|
+
pattern = "%Y-%m-%dT%H:%M:%S.%f%z"
|
|
458
|
+
else:
|
|
459
|
+
pattern = "%Y-%m-%dT%H:%M:%S%z"
|
|
460
|
+
s = datetime_object.strftime(pattern)
|
|
461
|
+
s = s[:-2] + ":" + s[-2:]
|
|
462
|
+
return s
|
|
236
463
|
|
|
237
464
|
@staticmethod
|
|
238
465
|
@check_arguments
|
|
@@ -249,9 +476,9 @@ class GeneralUtilities:
|
|
|
249
476
|
return True
|
|
250
477
|
type_of_argument = type(argument)
|
|
251
478
|
if type_of_argument == str:
|
|
252
|
-
return argument ==
|
|
479
|
+
return argument == GeneralUtilities.empty_string
|
|
253
480
|
else:
|
|
254
|
-
raise
|
|
481
|
+
raise ValueError(f"expected string-variable in argument of string_is_none_or_empty but the type was '{str(type_of_argument)}'")
|
|
255
482
|
|
|
256
483
|
@staticmethod
|
|
257
484
|
@check_arguments
|
|
@@ -259,7 +486,7 @@ class GeneralUtilities:
|
|
|
259
486
|
if GeneralUtilities.string_is_none_or_empty(string):
|
|
260
487
|
return True
|
|
261
488
|
else:
|
|
262
|
-
return string.strip() ==
|
|
489
|
+
return string.strip() == GeneralUtilities.empty_string
|
|
263
490
|
|
|
264
491
|
@staticmethod
|
|
265
492
|
@check_arguments
|
|
@@ -291,29 +518,60 @@ class GeneralUtilities:
|
|
|
291
518
|
@staticmethod
|
|
292
519
|
@check_arguments
|
|
293
520
|
def ends_with_newline_character(content: bytes) -> bool:
|
|
294
|
-
|
|
521
|
+
result = content.endswith(GeneralUtilities.string_to_bytes("\n"))
|
|
522
|
+
return result
|
|
295
523
|
|
|
296
524
|
@staticmethod
|
|
297
525
|
@check_arguments
|
|
298
|
-
def
|
|
526
|
+
def file_ends_with_content(file: str) -> bool:
|
|
299
527
|
content = GeneralUtilities.read_binary_from_file(file)
|
|
300
528
|
if len(content) == 0:
|
|
301
|
-
return
|
|
529
|
+
return False
|
|
302
530
|
else:
|
|
303
531
|
if GeneralUtilities.ends_with_newline_character(content):
|
|
304
|
-
return
|
|
532
|
+
return False
|
|
305
533
|
else:
|
|
306
|
-
return
|
|
534
|
+
return True
|
|
535
|
+
|
|
536
|
+
@staticmethod
|
|
537
|
+
@check_arguments
|
|
538
|
+
def get_new_line_character_for_textfile_if_required(file: str) -> bool:
|
|
539
|
+
if GeneralUtilities.file_ends_with_content(file):
|
|
540
|
+
return "\n"
|
|
541
|
+
else:
|
|
542
|
+
return GeneralUtilities.empty_string
|
|
307
543
|
|
|
308
544
|
@staticmethod
|
|
309
545
|
@check_arguments
|
|
310
546
|
def append_line_to_file(file: str, line: str, encoding: str = "utf-8") -> None:
|
|
311
|
-
|
|
312
|
-
|
|
547
|
+
GeneralUtilities.append_lines_to_file(file, [line], encoding)
|
|
548
|
+
|
|
549
|
+
@staticmethod
|
|
550
|
+
@check_arguments
|
|
551
|
+
def append_lines_to_file(file: str, lines: list[str], encoding: str = "utf-8") -> None:
|
|
552
|
+
if len(lines) == 0:
|
|
553
|
+
return
|
|
554
|
+
is_first_line = True
|
|
555
|
+
for line in lines:
|
|
556
|
+
insert_linebreak: bool
|
|
557
|
+
if is_first_line:
|
|
558
|
+
insert_linebreak = GeneralUtilities.file_ends_with_content(file)
|
|
559
|
+
else:
|
|
560
|
+
insert_linebreak = True
|
|
561
|
+
line_to_write: str = None
|
|
562
|
+
if insert_linebreak:
|
|
563
|
+
line_to_write = "\n"+line
|
|
564
|
+
else:
|
|
565
|
+
line_to_write = line
|
|
566
|
+
with open(file, "r+b") as fileObject:
|
|
567
|
+
fileObject.seek(0, os.SEEK_END)
|
|
568
|
+
fileObject.write(GeneralUtilities.string_to_bytes(line_to_write, encoding))
|
|
569
|
+
is_first_line = False
|
|
313
570
|
|
|
314
571
|
@staticmethod
|
|
315
572
|
@check_arguments
|
|
316
573
|
def append_to_file(file: str, content: str, encoding: str = "utf-8") -> None:
|
|
574
|
+
GeneralUtilities.assert_condition(not "\n" in content, "Appending multiple lines is not allowed. Use append_lines_to_file instead.")
|
|
317
575
|
with open(file, "a", encoding=encoding) as fileObject:
|
|
318
576
|
fileObject.write(content)
|
|
319
577
|
|
|
@@ -326,7 +584,7 @@ class GeneralUtilities:
|
|
|
326
584
|
@staticmethod
|
|
327
585
|
@check_arguments
|
|
328
586
|
def ensure_file_exists(path: str) -> None:
|
|
329
|
-
if(not os.path.isfile(path)):
|
|
587
|
+
if (not os.path.isfile(path)):
|
|
330
588
|
with open(path, "a+", encoding="utf-8"):
|
|
331
589
|
pass
|
|
332
590
|
|
|
@@ -338,21 +596,21 @@ class GeneralUtilities:
|
|
|
338
596
|
|
|
339
597
|
@staticmethod
|
|
340
598
|
@check_arguments
|
|
341
|
-
def
|
|
342
|
-
shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly)
|
|
599
|
+
def __rmtree(directory: str) -> None:
|
|
600
|
+
shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly) # pylint: disable=deprecated-argument
|
|
343
601
|
|
|
344
602
|
@staticmethod
|
|
345
603
|
@check_arguments
|
|
346
604
|
def ensure_directory_does_not_exist(path: str) -> None:
|
|
347
|
-
if(os.path.isdir(path)):
|
|
605
|
+
if (os.path.isdir(path)):
|
|
348
606
|
for root, dirs, files in os.walk(path, topdown=False):
|
|
349
607
|
for name in files:
|
|
350
608
|
filename = os.path.join(root, name)
|
|
351
609
|
os.chmod(filename, stat.S_IWUSR)
|
|
352
610
|
os.remove(filename)
|
|
353
611
|
for name in dirs:
|
|
354
|
-
GeneralUtilities.
|
|
355
|
-
GeneralUtilities.
|
|
612
|
+
GeneralUtilities.__rmtree(os.path.join(root, name))
|
|
613
|
+
GeneralUtilities.__rmtree(path)
|
|
356
614
|
|
|
357
615
|
@staticmethod
|
|
358
616
|
@check_arguments
|
|
@@ -370,9 +628,17 @@ class GeneralUtilities:
|
|
|
370
628
|
@staticmethod
|
|
371
629
|
@check_arguments
|
|
372
630
|
def ensure_file_does_not_exist(path: str) -> None:
|
|
373
|
-
if(os.path.isfile(path)):
|
|
631
|
+
if (os.path.isfile(path)):
|
|
374
632
|
os.remove(path)
|
|
375
633
|
|
|
634
|
+
@staticmethod
|
|
635
|
+
@check_arguments
|
|
636
|
+
def ensure_path_does_not_exist(path: str) -> None:
|
|
637
|
+
if (os.path.isfile(path)):
|
|
638
|
+
GeneralUtilities.ensure_file_does_not_exist(path)
|
|
639
|
+
if (os.path.isdir(path)):
|
|
640
|
+
GeneralUtilities.ensure_directory_does_not_exist(path)
|
|
641
|
+
|
|
376
642
|
@staticmethod
|
|
377
643
|
@check_arguments
|
|
378
644
|
def format_xml_file(filepath: str) -> None:
|
|
@@ -390,6 +656,7 @@ class GeneralUtilities:
|
|
|
390
656
|
@staticmethod
|
|
391
657
|
@check_arguments
|
|
392
658
|
def get_clusters_and_sectors_of_disk(diskpath: str) -> None:
|
|
659
|
+
GeneralUtilities.assert_condition(GeneralUtilities.current_system_is_windows(), "get_clusters_and_sectors_of_disk(diskpath) is only available on windows.")
|
|
393
660
|
sectorsPerCluster = ctypes.c_ulonglong(0)
|
|
394
661
|
bytesPerSector = ctypes.c_ulonglong(0)
|
|
395
662
|
rootPathName = ctypes.c_wchar_p(diskpath)
|
|
@@ -418,32 +685,61 @@ class GeneralUtilities:
|
|
|
418
685
|
result.append(fileB)
|
|
419
686
|
return result
|
|
420
687
|
|
|
688
|
+
@staticmethod
|
|
689
|
+
@check_arguments
|
|
690
|
+
def to_pascal_case(s: str) -> str:
|
|
691
|
+
return ''.join(current.lower() if prev.isalnum() else current.upper() for prev, current in zip(' ' + s, s) if current.isalnum())
|
|
692
|
+
|
|
693
|
+
@staticmethod
|
|
694
|
+
@check_arguments
|
|
695
|
+
def find_between(s: str, start: str, end: str) -> str:
|
|
696
|
+
return s.split(start)[1].split(end)[0]
|
|
697
|
+
|
|
421
698
|
@staticmethod
|
|
422
699
|
@check_arguments
|
|
423
700
|
def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None:
|
|
424
701
|
lines = [GeneralUtilities.strip_new_line_character(line) for line in lines]
|
|
425
|
-
content =
|
|
702
|
+
content = "\n".join(lines)
|
|
426
703
|
GeneralUtilities.write_text_to_file(file, content, encoding)
|
|
427
704
|
|
|
428
705
|
@staticmethod
|
|
429
706
|
@check_arguments
|
|
430
707
|
def write_text_to_file(file: str, content: str, encoding="utf-8") -> None:
|
|
431
|
-
GeneralUtilities.write_binary_to_file(file, bytearray(content, encoding))
|
|
708
|
+
GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding)))
|
|
432
709
|
|
|
433
710
|
@staticmethod
|
|
434
711
|
@check_arguments
|
|
435
|
-
def write_binary_to_file(file: str, content:
|
|
712
|
+
def write_binary_to_file(file: str, content: bytes) -> None:
|
|
436
713
|
with open(file, "wb") as file_object:
|
|
437
714
|
file_object.write(content)
|
|
438
715
|
|
|
716
|
+
@staticmethod
|
|
717
|
+
def is_binary_file(path: str):
|
|
718
|
+
content = GeneralUtilities.read_binary_from_file(path)
|
|
719
|
+
binary_content_indicators = [b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08', b'\x0E', b'\x1F']
|
|
720
|
+
for binary_content_indicator in binary_content_indicators:
|
|
721
|
+
if binary_content_indicator in content:
|
|
722
|
+
return True
|
|
723
|
+
return False
|
|
724
|
+
|
|
439
725
|
@staticmethod
|
|
440
726
|
@check_arguments
|
|
441
727
|
def read_lines_from_file(file: str, encoding="utf-8") -> list[str]:
|
|
442
|
-
|
|
728
|
+
content = GeneralUtilities.read_text_from_file(file, encoding)
|
|
729
|
+
if len(content) == 0:
|
|
730
|
+
return []
|
|
731
|
+
else:
|
|
732
|
+
return [GeneralUtilities.strip_new_line_character(line) for line in content.split('\n')]
|
|
733
|
+
|
|
734
|
+
@staticmethod
|
|
735
|
+
@check_arguments
|
|
736
|
+
def read_nonempty_lines_from_file(file: str, encoding="utf-8") -> list[str]:
|
|
737
|
+
return [line for line in GeneralUtilities.read_lines_from_file(file, encoding) if GeneralUtilities.string_has_content(line)]
|
|
443
738
|
|
|
444
739
|
@staticmethod
|
|
445
740
|
@check_arguments
|
|
446
741
|
def read_text_from_file(file: str, encoding="utf-8") -> str:
|
|
742
|
+
GeneralUtilities.assert_file_exists(file)
|
|
447
743
|
return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding)
|
|
448
744
|
|
|
449
745
|
@staticmethod
|
|
@@ -465,7 +761,7 @@ class GeneralUtilities:
|
|
|
465
761
|
@staticmethod
|
|
466
762
|
@check_arguments
|
|
467
763
|
def resolve_relative_path(path: str, base_path: str):
|
|
468
|
-
if(os.path.isabs(path)):
|
|
764
|
+
if (os.path.isabs(path)):
|
|
469
765
|
return path
|
|
470
766
|
else:
|
|
471
767
|
return str(Path(os.path.join(base_path, path)).resolve())
|
|
@@ -492,7 +788,7 @@ class GeneralUtilities:
|
|
|
492
788
|
with open(target_file, 'w', encoding='utf8') as f:
|
|
493
789
|
f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file))
|
|
494
790
|
else:
|
|
495
|
-
copyfile(source_file, target_file)
|
|
791
|
+
shutil.copyfile(source_file, target_file)
|
|
496
792
|
|
|
497
793
|
@staticmethod
|
|
498
794
|
@check_arguments
|
|
@@ -502,6 +798,12 @@ class GeneralUtilities:
|
|
|
502
798
|
except AttributeError:
|
|
503
799
|
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
|
504
800
|
|
|
801
|
+
@staticmethod
|
|
802
|
+
@check_arguments
|
|
803
|
+
def ensure_elevated_privileges() -> None:
|
|
804
|
+
if (not GeneralUtilities.current_user_has_elevated_privileges()):
|
|
805
|
+
raise ValueError("Not enough privileges.")
|
|
806
|
+
|
|
505
807
|
@staticmethod
|
|
506
808
|
@check_arguments
|
|
507
809
|
def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
@@ -515,12 +817,14 @@ class GeneralUtilities:
|
|
|
515
817
|
@check_arguments
|
|
516
818
|
def get_direct_files_of_folder(folder: str) -> list[str]:
|
|
517
819
|
result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))]
|
|
820
|
+
result = sorted(result, key=str.casefold)
|
|
518
821
|
return result
|
|
519
822
|
|
|
520
823
|
@staticmethod
|
|
521
824
|
@check_arguments
|
|
522
825
|
def get_direct_folders_of_folder(folder: str) -> list[str]:
|
|
523
826
|
result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
|
|
827
|
+
result = sorted(result, key=str.casefold)
|
|
524
828
|
return result
|
|
525
829
|
|
|
526
830
|
@staticmethod
|
|
@@ -530,6 +834,7 @@ class GeneralUtilities:
|
|
|
530
834
|
result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
|
|
531
835
|
for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
|
|
532
836
|
result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
|
|
837
|
+
result = sorted(result, key=str.casefold)
|
|
533
838
|
return result
|
|
534
839
|
|
|
535
840
|
@staticmethod
|
|
@@ -540,18 +845,19 @@ class GeneralUtilities:
|
|
|
540
845
|
result.extend(subfolders)
|
|
541
846
|
for subfolder in subfolders:
|
|
542
847
|
result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
|
|
848
|
+
result = sorted(result, key=str.casefold)
|
|
543
849
|
return result
|
|
544
850
|
|
|
545
851
|
@staticmethod
|
|
546
852
|
@check_arguments
|
|
547
853
|
def get_all_objects_of_folder(folder: str) -> list[str]:
|
|
548
|
-
return GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder)
|
|
854
|
+
return sorted(GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder), key=str.casefold)
|
|
549
855
|
|
|
550
856
|
@staticmethod
|
|
551
857
|
@check_arguments
|
|
552
858
|
def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
553
859
|
filename = Path(file).name
|
|
554
|
-
if(GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
|
|
860
|
+
if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
|
|
555
861
|
folder_of_file = os.path.dirname(file)
|
|
556
862
|
os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
|
|
557
863
|
|
|
@@ -559,7 +865,7 @@ class GeneralUtilities:
|
|
|
559
865
|
@check_arguments
|
|
560
866
|
def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
561
867
|
foldername = Path(folder).name
|
|
562
|
-
if(GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
|
|
868
|
+
if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
|
|
563
869
|
folder_of_folder = os.path.dirname(folder)
|
|
564
870
|
os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
|
|
565
871
|
|
|
@@ -624,12 +930,12 @@ class GeneralUtilities:
|
|
|
624
930
|
@check_arguments
|
|
625
931
|
def string_to_boolean(value: str) -> bool:
|
|
626
932
|
value = value.strip().lower()
|
|
627
|
-
if value in ('yes', '
|
|
933
|
+
if value in ('yes', 'y', 'true', 't', '1'):
|
|
628
934
|
return True
|
|
629
|
-
elif value in ('no', '
|
|
935
|
+
elif value in ('no', 'n', 'false', 'f', '0'):
|
|
630
936
|
return False
|
|
631
937
|
else:
|
|
632
|
-
raise
|
|
938
|
+
raise ValueError(f"Can not convert '{value}' to a boolean value")
|
|
633
939
|
|
|
634
940
|
@staticmethod
|
|
635
941
|
@check_arguments
|
|
@@ -643,16 +949,18 @@ class GeneralUtilities:
|
|
|
643
949
|
|
|
644
950
|
@staticmethod
|
|
645
951
|
@check_arguments
|
|
646
|
-
def get_time_based_logfile_by_folder(folder: str, name: str = "Log"
|
|
647
|
-
return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUtilities.get_time_based_logfilename(name
|
|
952
|
+
def get_time_based_logfile_by_folder(folder: str, name: str = "Log") -> str:
|
|
953
|
+
return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUtilities.get_time_based_logfilename(name)}.log")
|
|
648
954
|
|
|
649
955
|
@staticmethod
|
|
650
956
|
@check_arguments
|
|
651
|
-
def
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
957
|
+
def get_now() -> datetime:
|
|
958
|
+
return datetime.now().astimezone()
|
|
959
|
+
|
|
960
|
+
@staticmethod
|
|
961
|
+
@check_arguments
|
|
962
|
+
def get_time_based_logfilename(name: str = "Log") -> str:
|
|
963
|
+
d = GeneralUtilities.get_now()
|
|
656
964
|
return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}"
|
|
657
965
|
|
|
658
966
|
@staticmethod
|
|
@@ -669,14 +977,13 @@ class GeneralUtilities:
|
|
|
669
977
|
@check_arguments
|
|
670
978
|
def contains_line(lines, regex: str) -> bool:
|
|
671
979
|
for line in lines:
|
|
672
|
-
if(re.match(regex, line)):
|
|
980
|
+
if (re.match(regex, line)):
|
|
673
981
|
return True
|
|
674
982
|
return False
|
|
675
983
|
|
|
676
984
|
@staticmethod
|
|
677
985
|
@check_arguments
|
|
678
|
-
def read_csv_file(file: str, ignore_first_line: bool = False, treat_number_sign_at_begin_of_line_as_comment: bool = True, trim_values: bool = True,
|
|
679
|
-
encoding="utf-8", ignore_empty_lines: bool = True, separator_character: str = ";", values_are_surrounded_by_quotes: bool = False) -> list[str]:
|
|
986
|
+
def read_csv_file(file: str, ignore_first_line: bool = False, treat_number_sign_at_begin_of_line_as_comment: bool = True, trim_values: bool = True, encoding="utf-8", ignore_empty_lines: bool = True, separator_character: str = ";", values_are_surrounded_by_quotes: bool = False) -> list[list[str]]:
|
|
680
987
|
lines = GeneralUtilities.read_lines_from_file(file, encoding)
|
|
681
988
|
|
|
682
989
|
if ignore_first_line:
|
|
@@ -718,22 +1025,21 @@ class GeneralUtilities:
|
|
|
718
1025
|
@staticmethod
|
|
719
1026
|
@check_arguments
|
|
720
1027
|
def epew_is_available() -> bool:
|
|
1028
|
+
return GeneralUtilities.tool_is_available("epew")
|
|
1029
|
+
|
|
1030
|
+
@staticmethod
|
|
1031
|
+
@check_arguments
|
|
1032
|
+
def tool_is_available(toolname: str) -> bool:
|
|
721
1033
|
try:
|
|
722
|
-
return shutil.which(
|
|
1034
|
+
return shutil.which(toolname) is not None
|
|
723
1035
|
except:
|
|
724
1036
|
return False
|
|
725
1037
|
|
|
726
1038
|
@staticmethod
|
|
727
1039
|
@check_arguments
|
|
1040
|
+
@deprecated
|
|
728
1041
|
def absolute_file_paths(directory: str) -> list[str]:
|
|
729
|
-
|
|
730
|
-
for filename in filenames:
|
|
731
|
-
yield os.path.abspath(os.path.join(dirpath, filename))
|
|
732
|
-
|
|
733
|
-
@staticmethod
|
|
734
|
-
@check_arguments
|
|
735
|
-
def os_is_linux() -> bool:
|
|
736
|
-
return sys.platform in ('linux', 'linux2')
|
|
1042
|
+
return GeneralUtilities.get_all_files_of_folder(directory)
|
|
737
1043
|
|
|
738
1044
|
@staticmethod
|
|
739
1045
|
@check_arguments
|
|
@@ -741,7 +1047,7 @@ class GeneralUtilities:
|
|
|
741
1047
|
result = list()
|
|
742
1048
|
if list_as_string is not None:
|
|
743
1049
|
list_as_string = list_as_string.strip()
|
|
744
|
-
if list_as_string ==
|
|
1050
|
+
if list_as_string == GeneralUtilities.empty_string:
|
|
745
1051
|
pass
|
|
746
1052
|
elif separator in list_as_string:
|
|
747
1053
|
for item in list_as_string.split(separator):
|
|
@@ -765,6 +1071,202 @@ class GeneralUtilities:
|
|
|
765
1071
|
|
|
766
1072
|
@staticmethod
|
|
767
1073
|
@check_arguments
|
|
768
|
-
def
|
|
769
|
-
if
|
|
1074
|
+
def generate_password(length: int = 16, alphabet: str = None) -> None:
|
|
1075
|
+
if alphabet is None:
|
|
1076
|
+
alphabet = strin.ascii_letters + strin.digits+"_"
|
|
1077
|
+
return ''.join(secrets.choice(alphabet) for i in range(length))
|
|
1078
|
+
|
|
1079
|
+
@staticmethod
|
|
1080
|
+
@check_arguments
|
|
1081
|
+
def assert_condition(condition: bool, information: str = None) -> None:
|
|
1082
|
+
"""Throws an exception if the condition is false."""
|
|
1083
|
+
if (not condition):
|
|
1084
|
+
if information is None:
|
|
1085
|
+
information = "Internal assertion error."
|
|
770
1086
|
raise ValueError("Condition failed. "+information)
|
|
1087
|
+
|
|
1088
|
+
@staticmethod
|
|
1089
|
+
def current_system_is_windows():
|
|
1090
|
+
return platform.system() == 'Windows'
|
|
1091
|
+
|
|
1092
|
+
@staticmethod
|
|
1093
|
+
def current_system_is_linux():
|
|
1094
|
+
return platform.system() == 'Linux'
|
|
1095
|
+
|
|
1096
|
+
@staticmethod
|
|
1097
|
+
@check_arguments
|
|
1098
|
+
def get_line():
|
|
1099
|
+
return "--------------------------"
|
|
1100
|
+
|
|
1101
|
+
@staticmethod
|
|
1102
|
+
def get_longline():
|
|
1103
|
+
return GeneralUtilities.get_line() + GeneralUtilities.get_line()
|
|
1104
|
+
|
|
1105
|
+
@staticmethod
|
|
1106
|
+
@check_arguments
|
|
1107
|
+
def get_icon_check_empty(positive: bool) -> str:
|
|
1108
|
+
if positive:
|
|
1109
|
+
return "✅"
|
|
1110
|
+
else:
|
|
1111
|
+
return GeneralUtilities.empty_string
|
|
1112
|
+
|
|
1113
|
+
@staticmethod
|
|
1114
|
+
@check_arguments
|
|
1115
|
+
def get_icon_check_cross(positive: bool) -> str:
|
|
1116
|
+
if positive:
|
|
1117
|
+
return "✅"
|
|
1118
|
+
else:
|
|
1119
|
+
return "❌"
|
|
1120
|
+
|
|
1121
|
+
@staticmethod
|
|
1122
|
+
@check_arguments
|
|
1123
|
+
def get_certificate_expiry_date(certificate_file: str) -> datetime:
|
|
1124
|
+
with open(certificate_file, encoding="utf-8") as certificate_file_content:
|
|
1125
|
+
cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read())
|
|
1126
|
+
date_as_bytes = cert.get_notAfter()
|
|
1127
|
+
date_as_string = date_as_bytes.decode("utf-8")
|
|
1128
|
+
result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ')
|
|
1129
|
+
return result
|
|
1130
|
+
|
|
1131
|
+
@staticmethod
|
|
1132
|
+
@check_arguments
|
|
1133
|
+
def certificate_is_expired(certificate_file: str) -> bool:
|
|
1134
|
+
return GeneralUtilities.get_certificate_expiry_date(certificate_file) < GeneralUtilities.get_now()
|
|
1135
|
+
|
|
1136
|
+
@staticmethod
|
|
1137
|
+
@check_arguments
|
|
1138
|
+
def internet_connection_is_available() -> bool:
|
|
1139
|
+
# TODO add more hosts to check to return true if at least one is available
|
|
1140
|
+
try:
|
|
1141
|
+
with urllib.request.urlopen("https://www.google.com") as url_result:
|
|
1142
|
+
return (url_result.code // 100) == 2
|
|
1143
|
+
except:
|
|
1144
|
+
pass
|
|
1145
|
+
return False
|
|
1146
|
+
|
|
1147
|
+
@staticmethod
|
|
1148
|
+
@check_arguments
|
|
1149
|
+
def replace_variable_in_string(input_string: str, variable_name: str, variable_value: str) -> None:
|
|
1150
|
+
GeneralUtilities.assert_condition(not "__" in variable_name, f"'{variable_name}' is an invalid variable name because it contains '__' which is treated as control-sequence.")
|
|
1151
|
+
return input_string.replace(f"__[{variable_name}]__", variable_value)
|
|
1152
|
+
|
|
1153
|
+
@staticmethod
|
|
1154
|
+
@check_arguments
|
|
1155
|
+
def input(prompt: str, print_result: bool) -> str: # This function is a workaround for usescases like python scripts which calls input(...) using epew because then the prompt is not printed by the built-in-input-function.
|
|
1156
|
+
GeneralUtilities.write_message_to_stdout(prompt)
|
|
1157
|
+
result: str = input()
|
|
1158
|
+
if print_result:
|
|
1159
|
+
GeneralUtilities.write_message_to_stdout(f"Result: {result}")
|
|
1160
|
+
return result
|
|
1161
|
+
|
|
1162
|
+
@staticmethod
|
|
1163
|
+
@check_arguments
|
|
1164
|
+
def run_program_simple(program: str, arguments: list[str], cwd: str = None) -> tuple[int, str, str]:
|
|
1165
|
+
if cwd is None:
|
|
1166
|
+
cwd = os.getcwd()
|
|
1167
|
+
cmd = [program]+arguments
|
|
1168
|
+
with subprocess.Popen(cmd, cwd=cwd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as process:
|
|
1169
|
+
stdout, stderr = process.communicate()
|
|
1170
|
+
exit_code = process.wait()
|
|
1171
|
+
return (exit_code, stdout, stderr)
|
|
1172
|
+
|
|
1173
|
+
@staticmethod
|
|
1174
|
+
@check_arguments
|
|
1175
|
+
def assert_file_exists(file: str,message=None) -> None:
|
|
1176
|
+
if message is None:
|
|
1177
|
+
message=f"File '{file}' does not exist."
|
|
1178
|
+
GeneralUtilities.assert_condition(os.path.isfile(file), message)
|
|
1179
|
+
|
|
1180
|
+
@staticmethod
|
|
1181
|
+
@check_arguments
|
|
1182
|
+
def assert_file_does_not_exist(file: str,message=None) -> None:
|
|
1183
|
+
if message is None:
|
|
1184
|
+
message=f"File '{file}' exists."
|
|
1185
|
+
GeneralUtilities.assert_condition(not os.path.isfile(file), message)
|
|
1186
|
+
|
|
1187
|
+
@staticmethod
|
|
1188
|
+
@check_arguments
|
|
1189
|
+
def assert_folder_exists(folder: str,message=None) -> None:
|
|
1190
|
+
if message is None:
|
|
1191
|
+
message=f"Folder '{folder}' does not exist."
|
|
1192
|
+
GeneralUtilities.assert_condition(os.path.isdir(folder),message )
|
|
1193
|
+
|
|
1194
|
+
@staticmethod
|
|
1195
|
+
@check_arguments
|
|
1196
|
+
def assert_folder_does_not_exist(folder: str,message=None) -> None:
|
|
1197
|
+
if message is None:
|
|
1198
|
+
message= f"Folder '{folder}' exists."
|
|
1199
|
+
GeneralUtilities.assert_condition(not os.path.isdir(folder), f"Folder '{folder}' exists.")
|
|
1200
|
+
|
|
1201
|
+
@staticmethod
|
|
1202
|
+
@check_arguments
|
|
1203
|
+
def assert_not_null(obj,message:str=None) -> str:
|
|
1204
|
+
if message is None:
|
|
1205
|
+
message="Variable is not set"
|
|
1206
|
+
GeneralUtilities.assert_condition(obj is not None, message)
|
|
1207
|
+
|
|
1208
|
+
@staticmethod
|
|
1209
|
+
@check_arguments
|
|
1210
|
+
def retry_action(action, amount_of_attempts: int, action_name: str = None) -> None:
|
|
1211
|
+
amount_of_fails = 0
|
|
1212
|
+
enabled = True
|
|
1213
|
+
while enabled:
|
|
1214
|
+
try:
|
|
1215
|
+
result = action()
|
|
1216
|
+
return result
|
|
1217
|
+
except Exception:
|
|
1218
|
+
time.sleep(1.1)
|
|
1219
|
+
amount_of_fails = amount_of_fails+1
|
|
1220
|
+
GeneralUtilities.assert_condition(not (amount_of_attempts < amount_of_fails))
|
|
1221
|
+
message = f"Action failed {amount_of_attempts} times."
|
|
1222
|
+
if action_name is not None:
|
|
1223
|
+
message = f"{message} Name of action: {action_name}"
|
|
1224
|
+
GeneralUtilities.write_message_to_stderr(message)
|
|
1225
|
+
raise
|
|
1226
|
+
return None
|
|
1227
|
+
|
|
1228
|
+
@staticmethod
|
|
1229
|
+
@check_arguments
|
|
1230
|
+
def int_to_string(number: int, leading_zeroplaces: int, trailing_zeroplaces: int) -> str:
|
|
1231
|
+
return GeneralUtilities.float_to_string(float(number), leading_zeroplaces, trailing_zeroplaces)
|
|
1232
|
+
|
|
1233
|
+
@staticmethod
|
|
1234
|
+
@check_arguments
|
|
1235
|
+
def float_to_string(number: float, leading_zeroplaces: int, trailing_zeroplaces: int) -> str:
|
|
1236
|
+
plain_str = str(number)
|
|
1237
|
+
GeneralUtilities.assert_condition("." in plain_str)
|
|
1238
|
+
splitted: list[str] = plain_str.split(".")
|
|
1239
|
+
return splitted[0].zfill(leading_zeroplaces)+"."+splitted[1].ljust(trailing_zeroplaces, '0')
|
|
1240
|
+
|
|
1241
|
+
@staticmethod
|
|
1242
|
+
@check_arguments
|
|
1243
|
+
def process_is_running_by_name(process_name: str) -> bool:
|
|
1244
|
+
processes: list[psutil.Process] = list(psutil.process_iter())
|
|
1245
|
+
for p in processes:
|
|
1246
|
+
if p.name() == process_name:
|
|
1247
|
+
return True
|
|
1248
|
+
return False
|
|
1249
|
+
|
|
1250
|
+
@staticmethod
|
|
1251
|
+
@check_arguments
|
|
1252
|
+
def process_is_running_by_id(process_id: int) -> bool:
|
|
1253
|
+
processes: list[psutil.Process] = list(psutil.process_iter())
|
|
1254
|
+
for p in processes:
|
|
1255
|
+
if p.pid == process_id:
|
|
1256
|
+
return True
|
|
1257
|
+
return False
|
|
1258
|
+
|
|
1259
|
+
@staticmethod
|
|
1260
|
+
@check_arguments
|
|
1261
|
+
def kill_process(process_id: int, include_child_processes: bool) -> bool:
|
|
1262
|
+
if GeneralUtilities. process_is_running_by_id(process_id):
|
|
1263
|
+
GeneralUtilities.write_message_to_stdout(f"Process with id {process_id} is running. Terminating it...")
|
|
1264
|
+
process = psutil.Process(process_id)
|
|
1265
|
+
if include_child_processes:
|
|
1266
|
+
for child in process.children(recursive=True):
|
|
1267
|
+
if GeneralUtilities.process_is_running_by_id(child.pid):
|
|
1268
|
+
child.kill()
|
|
1269
|
+
if GeneralUtilities.process_is_running_by_id(process_id):
|
|
1270
|
+
process.kill()
|
|
1271
|
+
else:
|
|
1272
|
+
GeneralUtilities.write_message_to_stdout(f"Process with id {process_id} is not running anymore.")
|