ScriptCollection 3.5.10__py3-none-any.whl → 3.5.12__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.
@@ -1,869 +1,869 @@
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 __remove_readonly(func, path, _):
389
- os.chmod(path, stat.S_IWRITE)
390
- func(path)
391
-
392
- @staticmethod
393
- @check_arguments
394
- def rmtree(directory: str) -> None:
395
- shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly)
396
-
397
- @staticmethod
398
- @check_arguments
399
- def ensure_directory_does_not_exist(path: str) -> None:
400
- if (os.path.isdir(path)):
401
- for root, dirs, files in os.walk(path, topdown=False):
402
- for name in files:
403
- filename = os.path.join(root, name)
404
- os.chmod(filename, stat.S_IWUSR)
405
- os.remove(filename)
406
- for name in dirs:
407
- GeneralUtilities.rmtree(os.path.join(root, name))
408
- GeneralUtilities.rmtree(path)
409
-
410
- @staticmethod
411
- @check_arguments
412
- def ensure_folder_exists_and_is_empty(path: str) -> None:
413
- GeneralUtilities.ensure_directory_exists(path)
414
- for filename in os.listdir(path):
415
- file_path = os.path.join(path, filename)
416
- if os.path.isfile(file_path):
417
- os.remove(file_path)
418
- if os.path.islink(file_path):
419
- os.unlink(file_path)
420
- elif os.path.isdir(file_path):
421
- shutil.rmtree(file_path)
422
-
423
- @staticmethod
424
- @check_arguments
425
- def ensure_file_does_not_exist(path: str) -> None:
426
- if (os.path.isfile(path)):
427
- os.remove(path)
428
-
429
- @staticmethod
430
- @check_arguments
431
- def format_xml_file(filepath: str) -> None:
432
- GeneralUtilities.format_xml_file_with_encoding(filepath, "utf-8")
433
-
434
- @staticmethod
435
- @check_arguments
436
- def format_xml_file_with_encoding(filepath: str, encoding: str) -> None:
437
- with codecs.open(filepath, 'r', encoding=encoding) as file:
438
- text = file.read()
439
- text = parse(text).toprettyxml()
440
- with codecs.open(filepath, 'w', encoding=encoding) as file:
441
- file.write(text)
442
-
443
- @staticmethod
444
- @check_arguments
445
- def get_clusters_and_sectors_of_disk(diskpath: str) -> None:
446
- sectorsPerCluster = ctypes.c_ulonglong(0)
447
- bytesPerSector = ctypes.c_ulonglong(0)
448
- rootPathName = ctypes.c_wchar_p(diskpath)
449
- ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, ctypes.pointer(sectorsPerCluster), ctypes.pointer(bytesPerSector), None, None)
450
- return (sectorsPerCluster.value, bytesPerSector.value)
451
-
452
- @staticmethod
453
- @check_arguments
454
- def ensure_path_is_not_quoted(path: str) -> str:
455
- if (path.startswith("\"") and path.endswith("\"")) or (path.startswith("'") and path.endswith("'")):
456
- path = path[1:]
457
- path = path[:-1]
458
- return path
459
- else:
460
- return path
461
-
462
- @staticmethod
463
- @check_arguments
464
- def get_missing_files(folderA: str, folderB: str) -> list:
465
- folderA_length = len(folderA)
466
- result = []
467
- for fileA in GeneralUtilities.absolute_file_paths(folderA):
468
- file = fileA[folderA_length:]
469
- fileB = folderB + file
470
- if not os.path.isfile(fileB):
471
- result.append(fileB)
472
- return result
473
-
474
- @staticmethod
475
- @check_arguments
476
- def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None:
477
- lines = [GeneralUtilities.strip_new_line_character(line) for line in lines]
478
- content = os.linesep.join(lines)
479
- GeneralUtilities.write_text_to_file(file, content, encoding)
480
-
481
- @staticmethod
482
- @check_arguments
483
- def write_text_to_file(file: str, content: str, encoding="utf-8") -> None:
484
- GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding)))
485
-
486
- @staticmethod
487
- @check_arguments
488
- def write_binary_to_file(file: str, content: bytes) -> None:
489
- with open(file, "wb") as file_object:
490
- file_object.write(content)
491
-
492
- @staticmethod
493
- @check_arguments
494
- def read_lines_from_file(file: str, encoding="utf-8") -> list[str]:
495
- return [GeneralUtilities.strip_new_line_character(line) for line in GeneralUtilities.read_text_from_file(file, encoding).split('\n')]
496
-
497
- @staticmethod
498
- @check_arguments
499
- def read_text_from_file(file: str, encoding="utf-8") -> str:
500
- return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding)
501
-
502
- @staticmethod
503
- @check_arguments
504
- def read_binary_from_file(file: str) -> bytes:
505
- with open(file, "rb") as file_object:
506
- return file_object.read()
507
-
508
- @staticmethod
509
- @check_arguments
510
- def timedelta_to_simple_string(delta: timedelta) -> str:
511
- return (datetime(1970, 1, 1, 0, 0, 0) + delta).strftime('%H:%M:%S')
512
-
513
- @staticmethod
514
- @check_arguments
515
- def resolve_relative_path_from_current_working_directory(path: str) -> str:
516
- return GeneralUtilities.resolve_relative_path(path, os.getcwd())
517
-
518
- @staticmethod
519
- @check_arguments
520
- def resolve_relative_path(path: str, base_path: str):
521
- if (os.path.isabs(path)):
522
- return path
523
- else:
524
- return str(Path(os.path.join(base_path, path)).resolve())
525
-
526
- @staticmethod
527
- @check_arguments
528
- def get_metadata_for_file_for_clone_folder_structure(file: str) -> str:
529
- size = os.path.getsize(file)
530
- last_modified_timestamp = os.path.getmtime(file)
531
- hash_value = GeneralUtilities.get_sha256_of_file(file)
532
- last_access_timestamp = os.path.getatime(file)
533
- return f'{{"size":"{size}","sha256":"{hash_value}","mtime":"{last_modified_timestamp}","atime":"{last_access_timestamp}"}}'
534
-
535
- @staticmethod
536
- @check_arguments
537
- def clone_folder_structure(source: str, target: str, copy_only_metadata: bool):
538
- source = GeneralUtilities.resolve_relative_path(source, os.getcwd())
539
- target = GeneralUtilities.resolve_relative_path(target, os.getcwd())
540
- length_of_source = len(source)
541
- for source_file in GeneralUtilities.absolute_file_paths(source):
542
- target_file = target+source_file[length_of_source:]
543
- GeneralUtilities.ensure_directory_exists(os.path.dirname(target_file))
544
- if copy_only_metadata:
545
- with open(target_file, 'w', encoding='utf8') as f:
546
- f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file))
547
- else:
548
- copyfile(source_file, target_file)
549
-
550
- @staticmethod
551
- @check_arguments
552
- def current_user_has_elevated_privileges() -> bool:
553
- try:
554
- return os.getuid() == 0
555
- except AttributeError:
556
- return ctypes.windll.shell32.IsUserAnAdmin() == 1
557
-
558
- @staticmethod
559
- @check_arguments
560
- def ensure_elevated_privileges() -> None:
561
- if (not GeneralUtilities.current_user_has_elevated_privileges()):
562
- raise ValueError("Not enough privileges.")
563
-
564
- @staticmethod
565
- @check_arguments
566
- def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
567
- for file in GeneralUtilities.get_direct_files_of_folder(folder):
568
- GeneralUtilities.replace_in_filename(file, replace_from, replace_to, replace_only_full_match)
569
- for sub_folder in GeneralUtilities.get_direct_folders_of_folder(folder):
570
- GeneralUtilities.rename_names_of_all_files_and_folders(sub_folder, replace_from, replace_to, replace_only_full_match)
571
- GeneralUtilities.replace_in_foldername(folder, replace_from, replace_to, replace_only_full_match)
572
-
573
- @staticmethod
574
- @check_arguments
575
- def get_direct_files_of_folder(folder: str) -> list[str]:
576
- result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))]
577
- return result
578
-
579
- @staticmethod
580
- @check_arguments
581
- def get_direct_folders_of_folder(folder: str) -> list[str]:
582
- result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
583
- return result
584
-
585
- @staticmethod
586
- @check_arguments
587
- def get_all_files_of_folder(folder: str) -> list[str]:
588
- result = list()
589
- result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
590
- for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
591
- result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
592
- return result
593
-
594
- @staticmethod
595
- @check_arguments
596
- def get_all_folders_of_folder(folder: str) -> list[str]:
597
- result = list()
598
- subfolders = GeneralUtilities.get_direct_folders_of_folder(folder)
599
- result.extend(subfolders)
600
- for subfolder in subfolders:
601
- result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
602
- return result
603
-
604
- @staticmethod
605
- @check_arguments
606
- def get_all_objects_of_folder(folder: str) -> list[str]:
607
- return GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder)
608
-
609
- @staticmethod
610
- @check_arguments
611
- def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
612
- filename = Path(file).name
613
- if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
614
- folder_of_file = os.path.dirname(file)
615
- os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
616
-
617
- @staticmethod
618
- @check_arguments
619
- def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
620
- foldername = Path(folder).name
621
- if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
622
- folder_of_folder = os.path.dirname(folder)
623
- os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
624
-
625
- @staticmethod
626
- @check_arguments
627
- def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool:
628
- if replace_only_full_match:
629
- return input_text == search_text
630
- else:
631
- return search_text in input_text
632
-
633
- @staticmethod
634
- @check_arguments
635
- def str_none_safe(variable) -> str:
636
- if variable is None:
637
- return ''
638
- else:
639
- return str(variable)
640
-
641
- @staticmethod
642
- @check_arguments
643
- def arguments_to_array(arguments_as_string: str) -> list[str]:
644
- if arguments_as_string is None:
645
- return []
646
- if GeneralUtilities.string_has_content(arguments_as_string):
647
- return arguments_as_string.split(" ") # TODO this function should get improved to allow whitespaces in quote-substrings
648
- else:
649
- return []
650
-
651
- @staticmethod
652
- @check_arguments
653
- def arguments_to_array_for_log(arguments_as_string: str) -> list[str]:
654
- if arguments_as_string is None:
655
- return None
656
- return GeneralUtilities.arguments_to_array(arguments_as_string)
657
-
658
- @staticmethod
659
- @check_arguments
660
- def get_sha256_of_file(file: str) -> str:
661
- sha256 = hashlib.sha256()
662
- with open(file, "rb") as fileObject:
663
- for chunk in iter(lambda: fileObject.read(4096), b""):
664
- sha256.update(chunk)
665
- return sha256.hexdigest()
666
-
667
- @staticmethod
668
- @check_arguments
669
- def remove_duplicates(input_list) -> list:
670
- result = []
671
- for item in input_list:
672
- if not item in result:
673
- result.append(item)
674
- return result
675
-
676
- @staticmethod
677
- @check_arguments
678
- def print_stacktrace() -> None:
679
- for line in traceback.format_stack():
680
- GeneralUtilities.write_message_to_stdout(line.strip())
681
-
682
- @staticmethod
683
- @check_arguments
684
- def string_to_boolean(value: str) -> bool:
685
- value = value.strip().lower()
686
- if value in ('yes', 'y', 'true', 't', '1'):
687
- return True
688
- elif value in ('no', 'n', 'false', 'f', '0'):
689
- return False
690
- else:
691
- raise ValueError(f"Can not convert '{value}' to a boolean value")
692
-
693
- @staticmethod
694
- @check_arguments
695
- def file_is_empty(file: str) -> bool:
696
- return os.stat(file).st_size == 0
697
-
698
- @staticmethod
699
- @check_arguments
700
- def folder_is_empty(folder: str) -> bool:
701
- return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders_of_folder(folder)) == 0
702
-
703
- @staticmethod
704
- @check_arguments
705
- def get_time_based_logfile_by_folder(folder: str, name: str = "Log", in_utc: bool = False) -> str:
706
- return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUtilities.get_time_based_logfilename(name, in_utc)}.log")
707
-
708
- @staticmethod
709
- @check_arguments
710
- def get_time_based_logfilename(name: str = "Log", in_utc: bool = False) -> str:
711
- if (in_utc):
712
- d = datetime.utcnow()
713
- else:
714
- d = datetime.now()
715
- return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}"
716
-
717
- @staticmethod
718
- @check_arguments
719
- def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str:
720
- return payload.decode(encoding, errors="ignore")
721
-
722
- @staticmethod
723
- @check_arguments
724
- def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes:
725
- return payload.encode(encoding, errors="ignore")
726
-
727
- @staticmethod
728
- @check_arguments
729
- def contains_line(lines, regex: str) -> bool:
730
- for line in lines:
731
- if (re.match(regex, line)):
732
- return True
733
- return False
734
-
735
- @staticmethod
736
- @check_arguments
737
- 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,
738
- encoding="utf-8", ignore_empty_lines: bool = True, separator_character: str = ";", values_are_surrounded_by_quotes: bool = False) -> list[list[str]]:
739
- lines = GeneralUtilities.read_lines_from_file(file, encoding)
740
-
741
- if ignore_first_line:
742
- lines = lines[1:]
743
- result = list()
744
- line: str
745
- for line_loopvariable in lines:
746
- use_line = True
747
- line = line_loopvariable
748
-
749
- if trim_values:
750
- line = line.strip()
751
- if ignore_empty_lines:
752
- if not GeneralUtilities.string_has_content(line):
753
- use_line = False
754
-
755
- if treat_number_sign_at_begin_of_line_as_comment:
756
- if line.startswith("#"):
757
- use_line = False
758
-
759
- if use_line:
760
- if separator_character in line:
761
- raw_values_of_line = GeneralUtilities.to_list(line, separator_character)
762
- else:
763
- raw_values_of_line = [line]
764
- if trim_values:
765
- raw_values_of_line = [value.strip() for value in raw_values_of_line]
766
- values_of_line = []
767
- for raw_value_of_line in raw_values_of_line:
768
- value_of_line = raw_value_of_line
769
- if values_are_surrounded_by_quotes:
770
- value_of_line = value_of_line[1:]
771
- value_of_line = value_of_line[:-1]
772
- value_of_line = value_of_line.replace('""', '"')
773
- values_of_line.append(value_of_line)
774
- result.extend([values_of_line])
775
- return result
776
-
777
- @staticmethod
778
- @check_arguments
779
- def epew_is_available() -> bool:
780
- try:
781
- return shutil.which("epew") is not None
782
- except:
783
- return False
784
-
785
- @staticmethod
786
- @check_arguments
787
- @deprecated
788
- def absolute_file_paths(directory: str) -> list[str]:
789
- return GeneralUtilities.get_all_files_of_folder(directory)
790
-
791
- @staticmethod
792
- @check_arguments
793
- def to_list(list_as_string: str, separator: str = ",") -> list[str]:
794
- result = list()
795
- if list_as_string is not None:
796
- list_as_string = list_as_string.strip()
797
- if list_as_string == "":
798
- pass
799
- elif separator in list_as_string:
800
- for item in list_as_string.split(separator):
801
- result.append(item.strip())
802
- else:
803
- result.append(list_as_string)
804
- return result
805
-
806
- @staticmethod
807
- @check_arguments
808
- def get_next_square_number(number: int) -> int:
809
- GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative numbers")
810
- if number == 0:
811
- return 1
812
- root = 0
813
- square = 0
814
- while square < number:
815
- root = root+1
816
- square = root*root
817
- return root*root
818
-
819
- @staticmethod
820
- @check_arguments
821
- def generate_password(length: int = 16, alphabet: str = None) -> None:
822
- if alphabet is None:
823
- alphabet = strin.ascii_letters + strin.digits+"_"
824
- return ''.join(secrets.choice(alphabet) for i in range(length))
825
-
826
- @staticmethod
827
- @check_arguments
828
- def assert_condition(condition: bool, information: str) -> None:
829
- if (not condition):
830
- raise ValueError("Condition failed. "+information)
831
-
832
- @staticmethod
833
- def current_system_is_windows():
834
- return platform.system() == 'Windows'
835
-
836
- @staticmethod
837
- def current_system_is_linux():
838
- return platform.system() == 'Linux'
839
-
840
- @staticmethod
841
- @check_arguments
842
- def get_certificate_expiry_date(certificate_file: str) -> datetime:
843
- with open(certificate_file, encoding="utf-8") as certificate_file_content:
844
- cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read())
845
- date_as_bytes = cert.get_notAfter()
846
- date_as_string = date_as_bytes.decode("utf-8")
847
- result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ')
848
- return result
849
-
850
- @staticmethod
851
- @check_arguments
852
- def certificate_is_expired(certificate_file: str) -> bool:
853
- return GeneralUtilities.get_certificate_expiry_date(certificate_file) < datetime.now()
854
-
855
- @staticmethod
856
- @check_arguments
857
- def internet_connection_is_available() -> bool:
858
- # TODO add more hosts to check to return true if at least one is available
859
- try:
860
- with urllib.request.urlopen("https://google.com") as url_result:
861
- return (url_result.code // 100) == 2
862
- except:
863
- pass
864
- return False
865
-
866
- @staticmethod
867
- @check_arguments
868
- def replace_variable_in_string(input_string: str, variable_name: str, variable_value: str) -> None:
869
- 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 __remove_readonly(func, path, _):
389
+ os.chmod(path, stat.S_IWRITE)
390
+ func(path)
391
+
392
+ @staticmethod
393
+ @check_arguments
394
+ def rmtree(directory: str) -> None:
395
+ shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly)
396
+
397
+ @staticmethod
398
+ @check_arguments
399
+ def ensure_directory_does_not_exist(path: str) -> None:
400
+ if (os.path.isdir(path)):
401
+ for root, dirs, files in os.walk(path, topdown=False):
402
+ for name in files:
403
+ filename = os.path.join(root, name)
404
+ os.chmod(filename, stat.S_IWUSR)
405
+ os.remove(filename)
406
+ for name in dirs:
407
+ GeneralUtilities.rmtree(os.path.join(root, name))
408
+ GeneralUtilities.rmtree(path)
409
+
410
+ @staticmethod
411
+ @check_arguments
412
+ def ensure_folder_exists_and_is_empty(path: str) -> None:
413
+ GeneralUtilities.ensure_directory_exists(path)
414
+ for filename in os.listdir(path):
415
+ file_path = os.path.join(path, filename)
416
+ if os.path.isfile(file_path):
417
+ os.remove(file_path)
418
+ if os.path.islink(file_path):
419
+ os.unlink(file_path)
420
+ elif os.path.isdir(file_path):
421
+ shutil.rmtree(file_path)
422
+
423
+ @staticmethod
424
+ @check_arguments
425
+ def ensure_file_does_not_exist(path: str) -> None:
426
+ if (os.path.isfile(path)):
427
+ os.remove(path)
428
+
429
+ @staticmethod
430
+ @check_arguments
431
+ def format_xml_file(filepath: str) -> None:
432
+ GeneralUtilities.format_xml_file_with_encoding(filepath, "utf-8")
433
+
434
+ @staticmethod
435
+ @check_arguments
436
+ def format_xml_file_with_encoding(filepath: str, encoding: str) -> None:
437
+ with codecs.open(filepath, 'r', encoding=encoding) as file:
438
+ text = file.read()
439
+ text = parse(text).toprettyxml()
440
+ with codecs.open(filepath, 'w', encoding=encoding) as file:
441
+ file.write(text)
442
+
443
+ @staticmethod
444
+ @check_arguments
445
+ def get_clusters_and_sectors_of_disk(diskpath: str) -> None:
446
+ sectorsPerCluster = ctypes.c_ulonglong(0)
447
+ bytesPerSector = ctypes.c_ulonglong(0)
448
+ rootPathName = ctypes.c_wchar_p(diskpath)
449
+ ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, ctypes.pointer(sectorsPerCluster), ctypes.pointer(bytesPerSector), None, None)
450
+ return (sectorsPerCluster.value, bytesPerSector.value)
451
+
452
+ @staticmethod
453
+ @check_arguments
454
+ def ensure_path_is_not_quoted(path: str) -> str:
455
+ if (path.startswith("\"") and path.endswith("\"")) or (path.startswith("'") and path.endswith("'")):
456
+ path = path[1:]
457
+ path = path[:-1]
458
+ return path
459
+ else:
460
+ return path
461
+
462
+ @staticmethod
463
+ @check_arguments
464
+ def get_missing_files(folderA: str, folderB: str) -> list:
465
+ folderA_length = len(folderA)
466
+ result = []
467
+ for fileA in GeneralUtilities.absolute_file_paths(folderA):
468
+ file = fileA[folderA_length:]
469
+ fileB = folderB + file
470
+ if not os.path.isfile(fileB):
471
+ result.append(fileB)
472
+ return result
473
+
474
+ @staticmethod
475
+ @check_arguments
476
+ def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None:
477
+ lines = [GeneralUtilities.strip_new_line_character(line) for line in lines]
478
+ content = os.linesep.join(lines)
479
+ GeneralUtilities.write_text_to_file(file, content, encoding)
480
+
481
+ @staticmethod
482
+ @check_arguments
483
+ def write_text_to_file(file: str, content: str, encoding="utf-8") -> None:
484
+ GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding)))
485
+
486
+ @staticmethod
487
+ @check_arguments
488
+ def write_binary_to_file(file: str, content: bytes) -> None:
489
+ with open(file, "wb") as file_object:
490
+ file_object.write(content)
491
+
492
+ @staticmethod
493
+ @check_arguments
494
+ def read_lines_from_file(file: str, encoding="utf-8") -> list[str]:
495
+ return [GeneralUtilities.strip_new_line_character(line) for line in GeneralUtilities.read_text_from_file(file, encoding).split('\n')]
496
+
497
+ @staticmethod
498
+ @check_arguments
499
+ def read_text_from_file(file: str, encoding="utf-8") -> str:
500
+ return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding)
501
+
502
+ @staticmethod
503
+ @check_arguments
504
+ def read_binary_from_file(file: str) -> bytes:
505
+ with open(file, "rb") as file_object:
506
+ return file_object.read()
507
+
508
+ @staticmethod
509
+ @check_arguments
510
+ def timedelta_to_simple_string(delta: timedelta) -> str:
511
+ return (datetime(1970, 1, 1, 0, 0, 0) + delta).strftime('%H:%M:%S')
512
+
513
+ @staticmethod
514
+ @check_arguments
515
+ def resolve_relative_path_from_current_working_directory(path: str) -> str:
516
+ return GeneralUtilities.resolve_relative_path(path, os.getcwd())
517
+
518
+ @staticmethod
519
+ @check_arguments
520
+ def resolve_relative_path(path: str, base_path: str):
521
+ if (os.path.isabs(path)):
522
+ return path
523
+ else:
524
+ return str(Path(os.path.join(base_path, path)).resolve())
525
+
526
+ @staticmethod
527
+ @check_arguments
528
+ def get_metadata_for_file_for_clone_folder_structure(file: str) -> str:
529
+ size = os.path.getsize(file)
530
+ last_modified_timestamp = os.path.getmtime(file)
531
+ hash_value = GeneralUtilities.get_sha256_of_file(file)
532
+ last_access_timestamp = os.path.getatime(file)
533
+ return f'{{"size":"{size}","sha256":"{hash_value}","mtime":"{last_modified_timestamp}","atime":"{last_access_timestamp}"}}'
534
+
535
+ @staticmethod
536
+ @check_arguments
537
+ def clone_folder_structure(source: str, target: str, copy_only_metadata: bool):
538
+ source = GeneralUtilities.resolve_relative_path(source, os.getcwd())
539
+ target = GeneralUtilities.resolve_relative_path(target, os.getcwd())
540
+ length_of_source = len(source)
541
+ for source_file in GeneralUtilities.absolute_file_paths(source):
542
+ target_file = target+source_file[length_of_source:]
543
+ GeneralUtilities.ensure_directory_exists(os.path.dirname(target_file))
544
+ if copy_only_metadata:
545
+ with open(target_file, 'w', encoding='utf8') as f:
546
+ f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file))
547
+ else:
548
+ copyfile(source_file, target_file)
549
+
550
+ @staticmethod
551
+ @check_arguments
552
+ def current_user_has_elevated_privileges() -> bool:
553
+ try:
554
+ return os.getuid() == 0
555
+ except AttributeError:
556
+ return ctypes.windll.shell32.IsUserAnAdmin() == 1
557
+
558
+ @staticmethod
559
+ @check_arguments
560
+ def ensure_elevated_privileges() -> None:
561
+ if (not GeneralUtilities.current_user_has_elevated_privileges()):
562
+ raise ValueError("Not enough privileges.")
563
+
564
+ @staticmethod
565
+ @check_arguments
566
+ def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
567
+ for file in GeneralUtilities.get_direct_files_of_folder(folder):
568
+ GeneralUtilities.replace_in_filename(file, replace_from, replace_to, replace_only_full_match)
569
+ for sub_folder in GeneralUtilities.get_direct_folders_of_folder(folder):
570
+ GeneralUtilities.rename_names_of_all_files_and_folders(sub_folder, replace_from, replace_to, replace_only_full_match)
571
+ GeneralUtilities.replace_in_foldername(folder, replace_from, replace_to, replace_only_full_match)
572
+
573
+ @staticmethod
574
+ @check_arguments
575
+ def get_direct_files_of_folder(folder: str) -> list[str]:
576
+ result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))]
577
+ return result
578
+
579
+ @staticmethod
580
+ @check_arguments
581
+ def get_direct_folders_of_folder(folder: str) -> list[str]:
582
+ result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
583
+ return result
584
+
585
+ @staticmethod
586
+ @check_arguments
587
+ def get_all_files_of_folder(folder: str) -> list[str]:
588
+ result = list()
589
+ result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
590
+ for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
591
+ result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
592
+ return result
593
+
594
+ @staticmethod
595
+ @check_arguments
596
+ def get_all_folders_of_folder(folder: str) -> list[str]:
597
+ result = list()
598
+ subfolders = GeneralUtilities.get_direct_folders_of_folder(folder)
599
+ result.extend(subfolders)
600
+ for subfolder in subfolders:
601
+ result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
602
+ return result
603
+
604
+ @staticmethod
605
+ @check_arguments
606
+ def get_all_objects_of_folder(folder: str) -> list[str]:
607
+ return GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder)
608
+
609
+ @staticmethod
610
+ @check_arguments
611
+ def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
612
+ filename = Path(file).name
613
+ if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
614
+ folder_of_file = os.path.dirname(file)
615
+ os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
616
+
617
+ @staticmethod
618
+ @check_arguments
619
+ def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
620
+ foldername = Path(folder).name
621
+ if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
622
+ folder_of_folder = os.path.dirname(folder)
623
+ os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
624
+
625
+ @staticmethod
626
+ @check_arguments
627
+ def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool:
628
+ if replace_only_full_match:
629
+ return input_text == search_text
630
+ else:
631
+ return search_text in input_text
632
+
633
+ @staticmethod
634
+ @check_arguments
635
+ def str_none_safe(variable) -> str:
636
+ if variable is None:
637
+ return ''
638
+ else:
639
+ return str(variable)
640
+
641
+ @staticmethod
642
+ @check_arguments
643
+ def arguments_to_array(arguments_as_string: str) -> list[str]:
644
+ if arguments_as_string is None:
645
+ return []
646
+ if GeneralUtilities.string_has_content(arguments_as_string):
647
+ return arguments_as_string.split(" ") # TODO this function should get improved to allow whitespaces in quote-substrings
648
+ else:
649
+ return []
650
+
651
+ @staticmethod
652
+ @check_arguments
653
+ def arguments_to_array_for_log(arguments_as_string: str) -> list[str]:
654
+ if arguments_as_string is None:
655
+ return None
656
+ return GeneralUtilities.arguments_to_array(arguments_as_string)
657
+
658
+ @staticmethod
659
+ @check_arguments
660
+ def get_sha256_of_file(file: str) -> str:
661
+ sha256 = hashlib.sha256()
662
+ with open(file, "rb") as fileObject:
663
+ for chunk in iter(lambda: fileObject.read(4096), b""):
664
+ sha256.update(chunk)
665
+ return sha256.hexdigest()
666
+
667
+ @staticmethod
668
+ @check_arguments
669
+ def remove_duplicates(input_list) -> list:
670
+ result = []
671
+ for item in input_list:
672
+ if not item in result:
673
+ result.append(item)
674
+ return result
675
+
676
+ @staticmethod
677
+ @check_arguments
678
+ def print_stacktrace() -> None:
679
+ for line in traceback.format_stack():
680
+ GeneralUtilities.write_message_to_stdout(line.strip())
681
+
682
+ @staticmethod
683
+ @check_arguments
684
+ def string_to_boolean(value: str) -> bool:
685
+ value = value.strip().lower()
686
+ if value in ('yes', 'y', 'true', 't', '1'):
687
+ return True
688
+ elif value in ('no', 'n', 'false', 'f', '0'):
689
+ return False
690
+ else:
691
+ raise ValueError(f"Can not convert '{value}' to a boolean value")
692
+
693
+ @staticmethod
694
+ @check_arguments
695
+ def file_is_empty(file: str) -> bool:
696
+ return os.stat(file).st_size == 0
697
+
698
+ @staticmethod
699
+ @check_arguments
700
+ def folder_is_empty(folder: str) -> bool:
701
+ return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders_of_folder(folder)) == 0
702
+
703
+ @staticmethod
704
+ @check_arguments
705
+ def get_time_based_logfile_by_folder(folder: str, name: str = "Log", in_utc: bool = False) -> str:
706
+ return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUtilities.get_time_based_logfilename(name, in_utc)}.log")
707
+
708
+ @staticmethod
709
+ @check_arguments
710
+ def get_time_based_logfilename(name: str = "Log", in_utc: bool = False) -> str:
711
+ if (in_utc):
712
+ d = datetime.utcnow()
713
+ else:
714
+ d = datetime.now()
715
+ return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}"
716
+
717
+ @staticmethod
718
+ @check_arguments
719
+ def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str:
720
+ return payload.decode(encoding, errors="ignore")
721
+
722
+ @staticmethod
723
+ @check_arguments
724
+ def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes:
725
+ return payload.encode(encoding, errors="ignore")
726
+
727
+ @staticmethod
728
+ @check_arguments
729
+ def contains_line(lines, regex: str) -> bool:
730
+ for line in lines:
731
+ if (re.match(regex, line)):
732
+ return True
733
+ return False
734
+
735
+ @staticmethod
736
+ @check_arguments
737
+ 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,
738
+ encoding="utf-8", ignore_empty_lines: bool = True, separator_character: str = ";", values_are_surrounded_by_quotes: bool = False) -> list[list[str]]:
739
+ lines = GeneralUtilities.read_lines_from_file(file, encoding)
740
+
741
+ if ignore_first_line:
742
+ lines = lines[1:]
743
+ result = list()
744
+ line: str
745
+ for line_loopvariable in lines:
746
+ use_line = True
747
+ line = line_loopvariable
748
+
749
+ if trim_values:
750
+ line = line.strip()
751
+ if ignore_empty_lines:
752
+ if not GeneralUtilities.string_has_content(line):
753
+ use_line = False
754
+
755
+ if treat_number_sign_at_begin_of_line_as_comment:
756
+ if line.startswith("#"):
757
+ use_line = False
758
+
759
+ if use_line:
760
+ if separator_character in line:
761
+ raw_values_of_line = GeneralUtilities.to_list(line, separator_character)
762
+ else:
763
+ raw_values_of_line = [line]
764
+ if trim_values:
765
+ raw_values_of_line = [value.strip() for value in raw_values_of_line]
766
+ values_of_line = []
767
+ for raw_value_of_line in raw_values_of_line:
768
+ value_of_line = raw_value_of_line
769
+ if values_are_surrounded_by_quotes:
770
+ value_of_line = value_of_line[1:]
771
+ value_of_line = value_of_line[:-1]
772
+ value_of_line = value_of_line.replace('""', '"')
773
+ values_of_line.append(value_of_line)
774
+ result.extend([values_of_line])
775
+ return result
776
+
777
+ @staticmethod
778
+ @check_arguments
779
+ def epew_is_available() -> bool:
780
+ try:
781
+ return shutil.which("epew") is not None
782
+ except:
783
+ return False
784
+
785
+ @staticmethod
786
+ @check_arguments
787
+ @deprecated
788
+ def absolute_file_paths(directory: str) -> list[str]:
789
+ return GeneralUtilities.get_all_files_of_folder(directory)
790
+
791
+ @staticmethod
792
+ @check_arguments
793
+ def to_list(list_as_string: str, separator: str = ",") -> list[str]:
794
+ result = list()
795
+ if list_as_string is not None:
796
+ list_as_string = list_as_string.strip()
797
+ if list_as_string == "":
798
+ pass
799
+ elif separator in list_as_string:
800
+ for item in list_as_string.split(separator):
801
+ result.append(item.strip())
802
+ else:
803
+ result.append(list_as_string)
804
+ return result
805
+
806
+ @staticmethod
807
+ @check_arguments
808
+ def get_next_square_number(number: int) -> int:
809
+ GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative numbers")
810
+ if number == 0:
811
+ return 1
812
+ root = 0
813
+ square = 0
814
+ while square < number:
815
+ root = root+1
816
+ square = root*root
817
+ return root*root
818
+
819
+ @staticmethod
820
+ @check_arguments
821
+ def generate_password(length: int = 16, alphabet: str = None) -> None:
822
+ if alphabet is None:
823
+ alphabet = strin.ascii_letters + strin.digits+"_"
824
+ return ''.join(secrets.choice(alphabet) for i in range(length))
825
+
826
+ @staticmethod
827
+ @check_arguments
828
+ def assert_condition(condition: bool, information: str) -> None:
829
+ if (not condition):
830
+ raise ValueError("Condition failed. "+information)
831
+
832
+ @staticmethod
833
+ def current_system_is_windows():
834
+ return platform.system() == 'Windows'
835
+
836
+ @staticmethod
837
+ def current_system_is_linux():
838
+ return platform.system() == 'Linux'
839
+
840
+ @staticmethod
841
+ @check_arguments
842
+ def get_certificate_expiry_date(certificate_file: str) -> datetime:
843
+ with open(certificate_file, encoding="utf-8") as certificate_file_content:
844
+ cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read())
845
+ date_as_bytes = cert.get_notAfter()
846
+ date_as_string = date_as_bytes.decode("utf-8")
847
+ result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ')
848
+ return result
849
+
850
+ @staticmethod
851
+ @check_arguments
852
+ def certificate_is_expired(certificate_file: str) -> bool:
853
+ return GeneralUtilities.get_certificate_expiry_date(certificate_file) < datetime.now()
854
+
855
+ @staticmethod
856
+ @check_arguments
857
+ def internet_connection_is_available() -> bool:
858
+ # TODO add more hosts to check to return true if at least one is available
859
+ try:
860
+ with urllib.request.urlopen("https://google.com") as url_result:
861
+ return (url_result.code // 100) == 2
862
+ except:
863
+ pass
864
+ return False
865
+
866
+ @staticmethod
867
+ @check_arguments
868
+ def replace_variable_in_string(input_string: str, variable_name: str, variable_value: str) -> None:
869
+ return input_string.replace(f"__[{variable_name}]__", variable_value)