ScriptCollection 3.3.23__py3-none-any.whl → 4.0.78__py3-none-any.whl

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