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.
@@ -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 { 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
- result = sorted(result, key=str.casefold)
578
- return result
579
-
580
- @staticmethod
581
- @check_arguments
582
- def get_direct_folders_of_folder(folder: str) -> list[str]:
583
- result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
584
- result = sorted(result, key=str.casefold)
585
- return result
586
-
587
- @staticmethod
588
- @check_arguments
589
- def get_all_files_of_folder(folder: str) -> list[str]:
590
- result = list()
591
- result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
592
- for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
593
- result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
594
- result = sorted(result, key=str.casefold)
595
- return result
596
-
597
- @staticmethod
598
- @check_arguments
599
- def get_all_folders_of_folder(folder: str) -> list[str]:
600
- result = list()
601
- subfolders = GeneralUtilities.get_direct_folders_of_folder(folder)
602
- result.extend(subfolders)
603
- for subfolder in subfolders:
604
- result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
605
- result = sorted(result, key=str.casefold)
606
- return result
607
-
608
- @staticmethod
609
- @check_arguments
610
- def get_all_objects_of_folder(folder: str) -> list[str]:
611
- return sorted(GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder), key=str.casefold)
612
-
613
- @staticmethod
614
- @check_arguments
615
- def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
616
- filename = Path(file).name
617
- if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
618
- folder_of_file = os.path.dirname(file)
619
- os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
620
-
621
- @staticmethod
622
- @check_arguments
623
- def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
624
- foldername = Path(folder).name
625
- if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
626
- folder_of_folder = os.path.dirname(folder)
627
- os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
628
-
629
- @staticmethod
630
- @check_arguments
631
- def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool:
632
- if replace_only_full_match:
633
- return input_text == search_text
634
- else:
635
- return search_text in input_text
636
-
637
- @staticmethod
638
- @check_arguments
639
- def str_none_safe(variable) -> str:
640
- if variable is None:
641
- return ''
642
- else:
643
- return str(variable)
644
-
645
- @staticmethod
646
- @check_arguments
647
- def arguments_to_array(arguments_as_string: str) -> list[str]:
648
- if arguments_as_string is None:
649
- return []
650
- if GeneralUtilities.string_has_content(arguments_as_string):
651
- return arguments_as_string.split(" ") # TODO this function should get improved to allow whitespaces in quote-substrings
652
- else:
653
- return []
654
-
655
- @staticmethod
656
- @check_arguments
657
- def arguments_to_array_for_log(arguments_as_string: str) -> list[str]:
658
- if arguments_as_string is None:
659
- return None
660
- return GeneralUtilities.arguments_to_array(arguments_as_string)
661
-
662
- @staticmethod
663
- @check_arguments
664
- def get_sha256_of_file(file: str) -> str:
665
- sha256 = hashlib.sha256()
666
- with open(file, "rb") as fileObject:
667
- for chunk in iter(lambda: fileObject.read(4096), b""):
668
- sha256.update(chunk)
669
- return sha256.hexdigest()
670
-
671
- @staticmethod
672
- @check_arguments
673
- def remove_duplicates(input_list) -> list:
674
- result = []
675
- for item in input_list:
676
- if not item in result:
677
- result.append(item)
678
- return result
679
-
680
- @staticmethod
681
- @check_arguments
682
- def print_stacktrace() -> None:
683
- for line in traceback.format_stack():
684
- GeneralUtilities.write_message_to_stdout(line.strip())
685
-
686
- @staticmethod
687
- @check_arguments
688
- def string_to_boolean(value: str) -> bool:
689
- value = value.strip().lower()
690
- if value in ('yes', 'y', 'true', 't', '1'):
691
- return True
692
- elif value in ('no', 'n', 'false', 'f', '0'):
693
- return False
694
- else:
695
- raise ValueError(f"Can not convert '{value}' to a boolean value")
696
-
697
- @staticmethod
698
- @check_arguments
699
- def file_is_empty(file: str) -> bool:
700
- return os.stat(file).st_size == 0
701
-
702
- @staticmethod
703
- @check_arguments
704
- def folder_is_empty(folder: str) -> bool:
705
- return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders_of_folder(folder)) == 0
706
-
707
- @staticmethod
708
- @check_arguments
709
- def get_time_based_logfile_by_folder(folder: str, name: str = "Log", in_utc: bool = False) -> str:
710
- return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUtilities.get_time_based_logfilename(name, in_utc)}.log")
711
-
712
- @staticmethod
713
- @check_arguments
714
- def get_time_based_logfilename(name: str = "Log", in_utc: bool = False) -> str:
715
- if (in_utc):
716
- d = datetime.utcnow()
717
- else:
718
- d = datetime.now()
719
- return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}"
720
-
721
- @staticmethod
722
- @check_arguments
723
- def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str:
724
- return payload.decode(encoding, errors="ignore")
725
-
726
- @staticmethod
727
- @check_arguments
728
- def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes:
729
- return payload.encode(encoding, errors="ignore")
730
-
731
- @staticmethod
732
- @check_arguments
733
- def contains_line(lines, regex: str) -> bool:
734
- for line in lines:
735
- if (re.match(regex, line)):
736
- return True
737
- return False
738
-
739
- @staticmethod
740
- @check_arguments
741
- 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,
742
- encoding="utf-8", ignore_empty_lines: bool = True, separator_character: str = ";", values_are_surrounded_by_quotes: bool = False) -> list[list[str]]:
743
- lines = GeneralUtilities.read_lines_from_file(file, encoding)
744
-
745
- if ignore_first_line:
746
- lines = lines[1:]
747
- result = list()
748
- line: str
749
- for line_loopvariable in lines:
750
- use_line = True
751
- line = line_loopvariable
752
-
753
- if trim_values:
754
- line = line.strip()
755
- if ignore_empty_lines:
756
- if not GeneralUtilities.string_has_content(line):
757
- use_line = False
758
-
759
- if treat_number_sign_at_begin_of_line_as_comment:
760
- if line.startswith("#"):
761
- use_line = False
762
-
763
- if use_line:
764
- if separator_character in line:
765
- raw_values_of_line = GeneralUtilities.to_list(line, separator_character)
766
- else:
767
- raw_values_of_line = [line]
768
- if trim_values:
769
- raw_values_of_line = [value.strip() for value in raw_values_of_line]
770
- values_of_line = []
771
- for raw_value_of_line in raw_values_of_line:
772
- value_of_line = raw_value_of_line
773
- if values_are_surrounded_by_quotes:
774
- value_of_line = value_of_line[1:]
775
- value_of_line = value_of_line[:-1]
776
- value_of_line = value_of_line.replace('""', '"')
777
- values_of_line.append(value_of_line)
778
- result.extend([values_of_line])
779
- return result
780
-
781
- @staticmethod
782
- @check_arguments
783
- def epew_is_available() -> bool:
784
- try:
785
- return shutil.which("epew") is not None
786
- except:
787
- return False
788
-
789
- @staticmethod
790
- @check_arguments
791
- @deprecated
792
- def absolute_file_paths(directory: str) -> list[str]:
793
- return GeneralUtilities.get_all_files_of_folder(directory)
794
-
795
- @staticmethod
796
- @check_arguments
797
- def to_list(list_as_string: str, separator: str = ",") -> list[str]:
798
- result = list()
799
- if list_as_string is not None:
800
- list_as_string = list_as_string.strip()
801
- if list_as_string == "":
802
- pass
803
- elif separator in list_as_string:
804
- for item in list_as_string.split(separator):
805
- result.append(item.strip())
806
- else:
807
- result.append(list_as_string)
808
- return result
809
-
810
- @staticmethod
811
- @check_arguments
812
- def get_next_square_number(number: int) -> int:
813
- GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative numbers")
814
- if number == 0:
815
- return 1
816
- root = 0
817
- square = 0
818
- while square < number:
819
- root = root+1
820
- square = root*root
821
- return root*root
822
-
823
- @staticmethod
824
- @check_arguments
825
- def generate_password(length: int = 16, alphabet: str = None) -> None:
826
- if alphabet is None:
827
- alphabet = strin.ascii_letters + strin.digits+"_"
828
- return ''.join(secrets.choice(alphabet) for i in range(length))
829
-
830
- @staticmethod
831
- @check_arguments
832
- def assert_condition(condition: bool, information: str) -> None:
833
- if (not condition):
834
- raise ValueError("Condition failed. "+information)
835
-
836
- @staticmethod
837
- def current_system_is_windows():
838
- return platform.system() == 'Windows'
839
-
840
- @staticmethod
841
- def current_system_is_linux():
842
- return platform.system() == 'Linux'
843
-
844
- @staticmethod
845
- @check_arguments
846
- def get_certificate_expiry_date(certificate_file: str) -> datetime:
847
- with open(certificate_file, encoding="utf-8") as certificate_file_content:
848
- cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read())
849
- date_as_bytes = cert.get_notAfter()
850
- date_as_string = date_as_bytes.decode("utf-8")
851
- result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ')
852
- return result
853
-
854
- @staticmethod
855
- @check_arguments
856
- def certificate_is_expired(certificate_file: str) -> bool:
857
- return GeneralUtilities.get_certificate_expiry_date(certificate_file) < datetime.now()
858
-
859
- @staticmethod
860
- @check_arguments
861
- def internet_connection_is_available() -> bool:
862
- # TODO add more hosts to check to return true if at least one is available
863
- try:
864
- with urllib.request.urlopen("https://google.com") as url_result:
865
- return (url_result.code // 100) == 2
866
- except:
867
- pass
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)