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