scriptcollection 4.2.81__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ScriptCollection/AnionBuildPlatform.py +199 -0
- ScriptCollection/CertificateUpdater.py +149 -0
- ScriptCollection/Executables.py +921 -0
- ScriptCollection/GeneralUtilities.py +1589 -0
- ScriptCollection/HTTPMaintenanceOverheadHelper.py +36 -0
- ScriptCollection/OCIImages/AbstractImageHandler.py +38 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/ImageHandlerDebian.py +20 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/ImageHandlerDebianSlim.py +20 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/ImageHandlerGeneric.py +20 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/ImageHandlerGenericV.py +20 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/ImageHandlerGitlabCE.py +20 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/ImageHandlerGitlabEE.py +20 -0
- ScriptCollection/OCIImages/ConcreteImageHandlers/__init__.py +0 -0
- ScriptCollection/OCIImages/OCIImageManager.py +190 -0
- ScriptCollection/OCIImages/__init__.py +0 -0
- ScriptCollection/ProcessesRunner.py +43 -0
- ScriptCollection/ProgramRunnerBase.py +47 -0
- ScriptCollection/ProgramRunnerMock.py +2 -0
- ScriptCollection/ProgramRunnerPopen.py +57 -0
- ScriptCollection/ProgramRunnerSudo.py +108 -0
- ScriptCollection/Resources/CultureChooser/CultureChooser.js +29 -0
- ScriptCollection/Resources/CultureChooser/index.html +15 -0
- ScriptCollection/Resources/MaintenanceSite/MaintenanceSite.html +15 -0
- ScriptCollection/SCLog.py +115 -0
- ScriptCollection/ScriptCollectionCore.py +3485 -0
- ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py +192 -0
- ScriptCollection/TFCPS/Docker/__init__.py +0 -0
- ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationBase.py +8 -0
- ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationGenerate.py +6 -0
- ScriptCollection/TFCPS/DotNet/CertificateGeneratorInformationNoGenerate.py +7 -0
- ScriptCollection/TFCPS/DotNet/TFCPS_CodeUnitSpecific_DotNet.py +547 -0
- ScriptCollection/TFCPS/DotNet/__init__.py +0 -0
- ScriptCollection/TFCPS/Flutter/TFCPS_CodeUnitSpecific_Flutter.py +137 -0
- ScriptCollection/TFCPS/Flutter/__init__.py +0 -0
- ScriptCollection/TFCPS/Go/TFCPS_CodeUnitSpecific_Go.py +72 -0
- ScriptCollection/TFCPS/Go/__init__.py +0 -0
- ScriptCollection/TFCPS/Maven/TFCPS_CodeUnitSpecific_Maven.py +42 -0
- ScriptCollection/TFCPS/Maven/__init__.py +0 -0
- ScriptCollection/TFCPS/NodeJS/TFCPS_CodeUnitSpecific_NodeJS.py +232 -0
- ScriptCollection/TFCPS/NodeJS/__init__.py +0 -0
- ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py +239 -0
- ScriptCollection/TFCPS/Python/__init__.py +0 -0
- ScriptCollection/TFCPS/Rust/TFCPS_CodeUnitSpecific_Rust.py +42 -0
- ScriptCollection/TFCPS/Rust/__init__.py +0 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnitSpecific_Base.py +433 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnit_BuildCodeUnit.py +135 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnit_BuildCodeUnits.py +301 -0
- ScriptCollection/TFCPS/TFCPS_CreateRelease.py +98 -0
- ScriptCollection/TFCPS/TFCPS_Generic.py +44 -0
- ScriptCollection/TFCPS/TFCPS_MergeToMain.py +128 -0
- ScriptCollection/TFCPS/TFCPS_MergeToStable.py +356 -0
- ScriptCollection/TFCPS/TFCPS_PreBuildCodeunitsScript.py +48 -0
- ScriptCollection/TFCPS/TFCPS_Tools_General.py +1565 -0
- ScriptCollection/TFCPS/__init__.py +0 -0
- ScriptCollection/__init__.py +0 -0
- ScriptCollection/__pycache__/GeneralUtilities.cpython-311.pyc +0 -0
- ScriptCollection/__pycache__/__init__.cpython-311.pyc +0 -0
- scriptcollection-4.2.81.dist-info/METADATA +169 -0
- scriptcollection-4.2.81.dist-info/RECORD +62 -0
- scriptcollection-4.2.81.dist-info/WHEEL +5 -0
- scriptcollection-4.2.81.dist-info/entry_points.txt +67 -0
- scriptcollection-4.2.81.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
from os import listdir
|
|
4
|
+
from os.path import isfile, join, isdir
|
|
5
|
+
import platform
|
|
6
|
+
import json
|
|
7
|
+
import inspect
|
|
8
|
+
import ctypes
|
|
9
|
+
import hashlib
|
|
10
|
+
import subprocess
|
|
11
|
+
import shutil
|
|
12
|
+
import time
|
|
13
|
+
import urllib
|
|
14
|
+
import stat
|
|
15
|
+
import fnmatch
|
|
16
|
+
import secrets
|
|
17
|
+
import string as strin
|
|
18
|
+
import sys
|
|
19
|
+
from importlib import resources
|
|
20
|
+
from enum import Enum
|
|
21
|
+
import traceback
|
|
22
|
+
import warnings
|
|
23
|
+
import functools
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from datetime import datetime, timedelta, date, timezone
|
|
26
|
+
import typing
|
|
27
|
+
from packaging.version import Version
|
|
28
|
+
import psutil
|
|
29
|
+
from defusedxml.minidom import parse
|
|
30
|
+
from OpenSSL import crypto
|
|
31
|
+
|
|
32
|
+
class VersionEcholon(Enum):
|
|
33
|
+
LatestPatch = 0
|
|
34
|
+
LatestPatchOrLatestMinor = 1
|
|
35
|
+
LatestPatchOrLatestMinorOrNextMajor = 2
|
|
36
|
+
LatestVersion = 3
|
|
37
|
+
CustomAlgorithm = 4
|
|
38
|
+
|
|
39
|
+
class Platform(Enum):
|
|
40
|
+
Windows_AMD64 = 0
|
|
41
|
+
Linux_AMD64 = 1
|
|
42
|
+
Linux_ARM64 = 2
|
|
43
|
+
MacOS_ARM64 = 3
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Dependency:
|
|
47
|
+
dependencyname:str
|
|
48
|
+
current_version:str
|
|
49
|
+
|
|
50
|
+
def __init__(self,dependencyname:str,current_version:str):
|
|
51
|
+
self.dependencyname=dependencyname
|
|
52
|
+
self.current_version=current_version
|
|
53
|
+
|
|
54
|
+
class GeneralUtilities:
|
|
55
|
+
|
|
56
|
+
empty_string: str = ""
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def get_modest_dark_url() -> str:
|
|
60
|
+
return "https://aniondev.github.io/CDN/ScriptCollectionDesigns/ModestDark/Style.css"
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def is_generic(t: typing.Type):
|
|
64
|
+
return hasattr(t, "__origin__")
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def is_debugger_attached():
|
|
68
|
+
return sys.gettrace() is not None
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def check_arguments(function):
|
|
72
|
+
def __check_function(*args, **named_args):
|
|
73
|
+
parameters: list = inspect.getfullargspec(function)[0].copy()
|
|
74
|
+
arguments: list = list(tuple(args)).copy()
|
|
75
|
+
if "self" in parameters:
|
|
76
|
+
parameters.remove("self")
|
|
77
|
+
arguments.pop(0)
|
|
78
|
+
for index, argument in enumerate(arguments):
|
|
79
|
+
if argument is not None: # Check type of None is not possible. None is always a valid argument-value
|
|
80
|
+
if parameters[index] in function.__annotations__: # Check if a type-hint for parameter exist. If not, no parameter-type available for argument-type-check
|
|
81
|
+
# Check type of arguments if the type is a generic type seems to be impossible.
|
|
82
|
+
if not GeneralUtilities.is_generic(function.__annotations__[parameters[index]]):
|
|
83
|
+
if not isinstance(argument, function.__annotations__[parameters[index]]):
|
|
84
|
+
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)))
|
|
85
|
+
for index, named_argument in enumerate(named_args):
|
|
86
|
+
if named_args[named_argument] is not None:
|
|
87
|
+
if parameters[index] in function.__annotations__:
|
|
88
|
+
if not GeneralUtilities.is_generic(function.__annotations__.get(named_argument)):
|
|
89
|
+
if not isinstance(named_args[named_argument], function.__annotations__.get(named_argument)):
|
|
90
|
+
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)}")
|
|
91
|
+
return function(*args, **named_args)
|
|
92
|
+
__check_function.__doc__ = function.__doc__
|
|
93
|
+
return __check_function
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def deprecated(reason: str=None):
|
|
97
|
+
def decorator(func):
|
|
98
|
+
@functools.wraps(func)
|
|
99
|
+
def wrapper(*args, **kwargs):
|
|
100
|
+
msg = f"Function {func.__name__}() is deprecated."
|
|
101
|
+
if GeneralUtilities.string_has_content(reason):
|
|
102
|
+
msg += f" {reason}"
|
|
103
|
+
warnings.warn(
|
|
104
|
+
msg,
|
|
105
|
+
category=DeprecationWarning,
|
|
106
|
+
stacklevel=2
|
|
107
|
+
)
|
|
108
|
+
return func(*args, **kwargs)
|
|
109
|
+
return wrapper
|
|
110
|
+
return decorator
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
@check_arguments
|
|
114
|
+
def args_array_surround_with_quotes_if_required(arguments: list[str]) -> list[str]:
|
|
115
|
+
result = []
|
|
116
|
+
for argument in arguments:
|
|
117
|
+
if " " in argument and not (argument.startswith('"') and argument.endswith('"')):
|
|
118
|
+
result.append(f'"{argument}"')
|
|
119
|
+
else:
|
|
120
|
+
result.append(argument)
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
@check_arguments
|
|
125
|
+
def string_to_lines(string: str, add_empty_lines: bool = True, adapt_lines: bool = True) -> list[str]:
|
|
126
|
+
result : list[str] = list[str]()
|
|
127
|
+
lines : list[str] = list[str]()
|
|
128
|
+
if (string is not None):
|
|
129
|
+
if ("\n" in string):
|
|
130
|
+
lines = string.split("\n")
|
|
131
|
+
else:
|
|
132
|
+
lines.append(string)
|
|
133
|
+
for rawline in lines:
|
|
134
|
+
if adapt_lines:
|
|
135
|
+
line = rawline.replace("\r", "").strip()
|
|
136
|
+
else:
|
|
137
|
+
line = rawline
|
|
138
|
+
if GeneralUtilities.string_is_none_or_whitespace(line):
|
|
139
|
+
if add_empty_lines:
|
|
140
|
+
result.append(line)
|
|
141
|
+
else:
|
|
142
|
+
result.append(line)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
@check_arguments
|
|
147
|
+
def string_to_datetime(value: str) -> datetime:
|
|
148
|
+
"""expects a value in the format 2022-10-06T19:26:01"""
|
|
149
|
+
if "." in value:
|
|
150
|
+
value = value.split(".")[0]
|
|
151
|
+
return datetime.strptime(value,"%Y-%m-%dT%H:%M:%S")
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
@check_arguments
|
|
155
|
+
def datetime_to_string(value: datetime) -> str:
|
|
156
|
+
"""returns a value in the format 2022-10-06T19:26:01"""
|
|
157
|
+
return value.strftime("%Y-%m-%dT%H:%M:%S")
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
@check_arguments
|
|
161
|
+
def string_to_date(value: str) -> date:
|
|
162
|
+
"""expects a value in the format 2022-10-06"""
|
|
163
|
+
splitted = value.split("-")
|
|
164
|
+
return date(int(splitted[0]), int(splitted[1]), int(splitted[2]))
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
@check_arguments
|
|
168
|
+
def date_to_string(value: date) -> str:
|
|
169
|
+
"""returns a value in the format 2022-10-06"""
|
|
170
|
+
return value.strftime("%Y-%m-%d")
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
@check_arguments
|
|
174
|
+
def copy_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False, ignored_glob_patterms: list[str] = None) -> None:
|
|
175
|
+
GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, False, ignored_glob_patterms)
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
@check_arguments
|
|
179
|
+
def move_content_of_folder(source_directory: str, target_directory: str, overwrite_existing_files=False, ignored_glob_patterms: list[str] = None) -> None:
|
|
180
|
+
GeneralUtilities.__copy_or_move_content_of_folder(source_directory, target_directory, overwrite_existing_files, True, ignored_glob_patterms)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
@check_arguments
|
|
184
|
+
def merge_dependency_lists(versions:list[list[Dependency]]) -> dict[str,set[str]]:
|
|
185
|
+
result:dict[str,set[str]]=dict[str,set[str]]()
|
|
186
|
+
for dlist in versions:
|
|
187
|
+
for ditem in dlist:
|
|
188
|
+
if not ditem.dependencyname in result:
|
|
189
|
+
result[ditem.dependencyname]=set[str]()
|
|
190
|
+
result[ditem.dependencyname].add(ditem.current_version)
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
@check_arguments
|
|
195
|
+
def choose_version(available_versions:list[str],current_version:str,echolon:VersionEcholon) -> str:
|
|
196
|
+
match echolon:
|
|
197
|
+
case VersionEcholon.LatestPatch:
|
|
198
|
+
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)}."))
|
|
199
|
+
case VersionEcholon.LatestPatchOrLatestMinor:
|
|
200
|
+
return GeneralUtilities.get_latest_version(GeneralUtilities.filter_versions_by_prefix(available_versions,f"{GeneralUtilities.get_major_part_of_version(current_version)}."))
|
|
201
|
+
case VersionEcholon.LatestPatchOrLatestMinorOrNextMajor:
|
|
202
|
+
raise ValueError("not implemented")#TODO
|
|
203
|
+
case VersionEcholon.LatestVersion:
|
|
204
|
+
return GeneralUtilities.get_latest_version(available_versions)
|
|
205
|
+
case _:
|
|
206
|
+
raise ValueError("Unknown echolon-value: "+str(echolon))
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
@check_arguments
|
|
210
|
+
def get_latest_version(versions:list[str]) -> str:
|
|
211
|
+
GeneralUtilities.assert_condition(0<len(versions),"Version-list can not be empty.")
|
|
212
|
+
latest = max(versions, key=Version)
|
|
213
|
+
return latest
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
@check_arguments
|
|
217
|
+
def filter_versions_by_prefix(versions:list[str],prefix:str) -> list[str]:
|
|
218
|
+
return [v for v in versions if v.startswith(prefix)]
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
@check_arguments
|
|
222
|
+
def get_major_part_of_version(version:str) -> int:
|
|
223
|
+
return GeneralUtilities.get_version_parts(version)[0]
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
@check_arguments
|
|
227
|
+
def get_minor_part_of_version(version:str) -> int:
|
|
228
|
+
return GeneralUtilities.get_version_parts(version)[1]
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
@check_arguments
|
|
232
|
+
def get_patch_part_of_version(version:str) -> int:
|
|
233
|
+
return GeneralUtilities.get_version_parts(version)[2]
|
|
234
|
+
|
|
235
|
+
@staticmethod
|
|
236
|
+
@check_arguments
|
|
237
|
+
def get_version_parts(version:str) -> tuple[int,int,int]:
|
|
238
|
+
match = re.match(r"^(\d+).(\d+).(\d+)$", version)
|
|
239
|
+
GeneralUtilities.assert_condition(match is not None,f"string \"{version}\" is not a valid version.")
|
|
240
|
+
return (int(match.group(1)),int(match.group(2)),int(match.group(3)))
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
@check_arguments
|
|
244
|
+
def is_ignored_by_glob_pattern(source_directory:str,path:str, ignored_glob_patterms: list[str]) -> bool:
|
|
245
|
+
source_directory=GeneralUtilities.normalize_path(source_directory)
|
|
246
|
+
path=GeneralUtilities.normalize_path(path)
|
|
247
|
+
GeneralUtilities.assert_condition(path.startswith(source_directory), f"Path '{path}' is not located in source directory '{source_directory}'.")
|
|
248
|
+
if ignored_glob_patterms is None:
|
|
249
|
+
return False
|
|
250
|
+
relative_path = os.path.relpath(path, source_directory)
|
|
251
|
+
for pattern in ignored_glob_patterms:
|
|
252
|
+
if fnmatch.filter([relative_path], pattern):
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
@check_arguments
|
|
258
|
+
def safe_copy(src:str, dst:str):
|
|
259
|
+
shutil.copy2(src, dst)
|
|
260
|
+
src_size = os.path.getsize(src)
|
|
261
|
+
dst_size = os.path.getsize(dst)
|
|
262
|
+
if src_size != dst_size:
|
|
263
|
+
raise IOError(f"Copy failed: size mismatch {src} ({src_size}) != {dst} ({dst_size})")
|
|
264
|
+
|
|
265
|
+
@staticmethod
|
|
266
|
+
@check_arguments
|
|
267
|
+
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:
|
|
268
|
+
srcDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(source_directory)
|
|
269
|
+
dstDirFull = GeneralUtilities.resolve_relative_path_from_current_working_directory(target_directory)
|
|
270
|
+
if (os.path.isdir(source_directory)):
|
|
271
|
+
GeneralUtilities.ensure_directory_exists(target_directory)
|
|
272
|
+
for file in GeneralUtilities.get_direct_files_of_folder(srcDirFull):
|
|
273
|
+
filename = os.path.basename(file)
|
|
274
|
+
if not GeneralUtilities.is_ignored_by_glob_pattern(source_directory,file, ignored_glob_patterms):
|
|
275
|
+
targetfile = os.path.join(dstDirFull, filename)
|
|
276
|
+
if (os.path.isfile(targetfile)):
|
|
277
|
+
if overwrite_existing_files:
|
|
278
|
+
GeneralUtilities.ensure_file_does_not_exist(targetfile)
|
|
279
|
+
else:
|
|
280
|
+
raise ValueError(f"Targetfile '{targetfile}' does already exist.")
|
|
281
|
+
GeneralUtilities.safe_copy(file, targetfile)
|
|
282
|
+
if remove_source:
|
|
283
|
+
GeneralUtilities.ensure_file_does_not_exist(file)#remove file to save storage
|
|
284
|
+
for sub_folder in GeneralUtilities.get_direct_folders_of_folder(srcDirFull):
|
|
285
|
+
if not GeneralUtilities.is_ignored_by_glob_pattern(source_directory,sub_folder, ignored_glob_patterms):
|
|
286
|
+
foldername = os.path.basename(sub_folder)
|
|
287
|
+
sub_target = os.path.join(dstDirFull, foldername)
|
|
288
|
+
GeneralUtilities.__copy_or_move_content_of_folder(sub_folder, sub_target, overwrite_existing_files, remove_source,ignored_glob_patterms)
|
|
289
|
+
if remove_source:
|
|
290
|
+
GeneralUtilities.ensure_directory_does_not_exist(sub_folder)
|
|
291
|
+
else:
|
|
292
|
+
raise ValueError(f"Folder '{source_directory}' does not exist")
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
@check_arguments
|
|
296
|
+
def replace_regex_each_line_of_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8", verbose: bool = False) -> None:
|
|
297
|
+
if verbose:
|
|
298
|
+
GeneralUtilities.write_message_to_stdout(f"Replace '{replace_from_regex}' to '{replace_to_regex}' in '{file}'")
|
|
299
|
+
lines=GeneralUtilities.read_lines_from_file(file,encoding)
|
|
300
|
+
replaced_lines = []
|
|
301
|
+
for line in lines:
|
|
302
|
+
replaced_line = re.sub(replace_from_regex, replace_to_regex, line)
|
|
303
|
+
replaced_lines.append(replaced_line)
|
|
304
|
+
GeneralUtilities.write_lines_to_file(file,replaced_lines,encoding)
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
@check_arguments
|
|
308
|
+
def replace_regex_in_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8") -> None:
|
|
309
|
+
with open(file, encoding=encoding, mode="r") as f:
|
|
310
|
+
content = f.read()
|
|
311
|
+
content = re.sub(replace_from_regex, replace_to_regex, content)
|
|
312
|
+
with open(file, encoding=encoding, mode="w") as f:
|
|
313
|
+
f.write(content)
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
@check_arguments
|
|
317
|
+
def replace_xmltag_in_file(file: str, tag: str, new_value: str, encoding="utf-8") -> None:
|
|
318
|
+
GeneralUtilities.assert_condition(tag.isalnum(tag), f"Invalid tag: \"{tag}\"")
|
|
319
|
+
GeneralUtilities.replace_regex_in_file(file, f"<{tag}>.*</{tag}>", f"<{tag}>{new_value}</{tag}>", encoding)
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
@check_arguments
|
|
323
|
+
def update_version_in_csproj_file(file: str, target_version: str) -> None:
|
|
324
|
+
GeneralUtilities.replace_xmltag_in_file(file, "Version", target_version)
|
|
325
|
+
GeneralUtilities.replace_xmltag_in_file(file, "AssemblyVersion", target_version + ".0")
|
|
326
|
+
GeneralUtilities.replace_xmltag_in_file(file, "FileVersion", target_version + ".0")
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
@check_arguments
|
|
330
|
+
def replace_underscores_in_text(text: str, replacements: dict) -> str:
|
|
331
|
+
changed = True
|
|
332
|
+
while changed:
|
|
333
|
+
changed = False
|
|
334
|
+
for key, value in replacements.items():
|
|
335
|
+
previousValue = text
|
|
336
|
+
text = text.replace(f"__{key}__", value)
|
|
337
|
+
if (not text == previousValue):
|
|
338
|
+
changed = True
|
|
339
|
+
return text
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
@check_arguments
|
|
343
|
+
def replace_underscores_in_file(file: str, replacements: dict, encoding: str = "utf-8"):
|
|
344
|
+
text = GeneralUtilities.read_text_from_file(file, encoding)
|
|
345
|
+
text = GeneralUtilities.replace_underscores_in_text(text, replacements)
|
|
346
|
+
GeneralUtilities.write_text_to_file(file, text, encoding)
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
@check_arguments
|
|
350
|
+
def print_text(text: str, print_to_stdout: bool = True):
|
|
351
|
+
stream: object = sys.stdout if print_to_stdout else sys.stderr
|
|
352
|
+
GeneralUtilities.__print_text_to_console(text, stream)
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
@check_arguments
|
|
356
|
+
def print_text_in_green(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
357
|
+
GeneralUtilities.print_text_in_color(text, 32, print_to_stdout, print_as_color)
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
@check_arguments
|
|
361
|
+
def print_text_in_yellow(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
362
|
+
GeneralUtilities.print_text_in_color(text, 33, print_to_stdout, print_as_color)
|
|
363
|
+
|
|
364
|
+
@staticmethod
|
|
365
|
+
@check_arguments
|
|
366
|
+
def print_text_in_red(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
367
|
+
GeneralUtilities.print_text_in_color(text, 31, print_to_stdout, print_as_color)
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
@check_arguments
|
|
371
|
+
def print_text_in_cyan(text: str, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
372
|
+
GeneralUtilities.print_text_in_color(text, 36, print_to_stdout, print_as_color)
|
|
373
|
+
|
|
374
|
+
@staticmethod
|
|
375
|
+
@check_arguments
|
|
376
|
+
def print_text_in_color(text: str, colorcode: int, print_to_stdout: bool = True, print_as_color: bool = True):
|
|
377
|
+
stream: object = sys.stdout if print_to_stdout else sys.stderr
|
|
378
|
+
if print_as_color:
|
|
379
|
+
text = f"\033[{colorcode}m{text}\033[0m"
|
|
380
|
+
GeneralUtilities.__print_text_to_console(text, stream)
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
@check_arguments
|
|
384
|
+
def __print_text_to_console(text: str, stream: object):
|
|
385
|
+
stream.write(text)
|
|
386
|
+
stream.flush()
|
|
387
|
+
|
|
388
|
+
@staticmethod
|
|
389
|
+
@check_arguments
|
|
390
|
+
def reconfigure_standard_input_and_outputs():
|
|
391
|
+
sys.stdin.reconfigure(encoding='utf-8')
|
|
392
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
393
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
@check_arguments
|
|
397
|
+
def write_message_to_stdout_advanced(message: str, add_empty_lines: bool = True, adapt_lines: bool = True, append_linebreak: bool = True):
|
|
398
|
+
new_line_character: str = "\n" if append_linebreak else GeneralUtilities.empty_string
|
|
399
|
+
for line in GeneralUtilities.string_to_lines(message, add_empty_lines, adapt_lines):
|
|
400
|
+
sys.stdout.write(GeneralUtilities.str_none_safe(line)+new_line_character)
|
|
401
|
+
sys.stdout.flush()
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
@check_arguments
|
|
405
|
+
def write_message_to_stdout(message: str):
|
|
406
|
+
GeneralUtilities.write_message_to_stdout_advanced(message, True, True, True)
|
|
407
|
+
|
|
408
|
+
@staticmethod
|
|
409
|
+
@check_arguments
|
|
410
|
+
def write_message_to_stderr_advanced(message: str, add_empty_lines: bool = True, adapt_lines: bool = True, append_linebreak: bool = True):
|
|
411
|
+
new_line_character: str = "\n" if append_linebreak else GeneralUtilities.empty_string
|
|
412
|
+
for line in GeneralUtilities.string_to_lines(message, add_empty_lines, adapt_lines):
|
|
413
|
+
sys.stderr.write(GeneralUtilities.str_none_safe(line)+new_line_character)
|
|
414
|
+
sys.stderr.flush()
|
|
415
|
+
|
|
416
|
+
@staticmethod
|
|
417
|
+
@check_arguments
|
|
418
|
+
def write_message_to_stderr(message: str):
|
|
419
|
+
GeneralUtilities.write_message_to_stderr_advanced(message, True, True, True)
|
|
420
|
+
|
|
421
|
+
@staticmethod
|
|
422
|
+
@check_arguments
|
|
423
|
+
def get_advanced_errormessage_for_os_error(os_error: OSError) -> str:
|
|
424
|
+
if GeneralUtilities.string_has_content(os_error.filename2):
|
|
425
|
+
secondpath = f" {os_error.filename2}"
|
|
426
|
+
else:
|
|
427
|
+
secondpath = GeneralUtilities.empty_string
|
|
428
|
+
return f"Related path(s): {os_error.filename}{secondpath}"
|
|
429
|
+
|
|
430
|
+
@staticmethod
|
|
431
|
+
@check_arguments
|
|
432
|
+
def write_exception_to_stderr(exception: Exception, extra_message: str = None):
|
|
433
|
+
GeneralUtilities.write_exception_to_stderr_with_traceback(exception, None, extra_message)
|
|
434
|
+
|
|
435
|
+
@staticmethod
|
|
436
|
+
@check_arguments
|
|
437
|
+
def write_exception_to_stderr_with_traceback(exception: Exception, current_traceback=None, extra_message: str = None):
|
|
438
|
+
GeneralUtilities.write_message_to_stderr(GeneralUtilities.exception_to_str(exception,current_traceback,extra_message))
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
@check_arguments
|
|
442
|
+
def exception_to_str(exception: Exception, current_traceback=None, extra_message: str = None)->str:
|
|
443
|
+
result=""
|
|
444
|
+
result=result+"Exception("
|
|
445
|
+
result=result+"\n Type: " + str(type(exception))
|
|
446
|
+
result=result+"\n Message: " + str(exception)
|
|
447
|
+
if extra_message is not None:
|
|
448
|
+
result=result+"\n Extra-message: " + str(extra_message)
|
|
449
|
+
if isinstance(exception, OSError):
|
|
450
|
+
result=result+"\n "+GeneralUtilities.get_advanced_errormessage_for_os_error(exception)
|
|
451
|
+
if current_traceback is not None:
|
|
452
|
+
result=result+"\n Traceback:\n" +str(current_traceback.format_exc())
|
|
453
|
+
result=result+"\n)"
|
|
454
|
+
return result
|
|
455
|
+
|
|
456
|
+
@staticmethod
|
|
457
|
+
@check_arguments
|
|
458
|
+
def string_has_content(string: str) -> bool:
|
|
459
|
+
if string is None:
|
|
460
|
+
return False
|
|
461
|
+
else:
|
|
462
|
+
return len(string.strip()) > 0
|
|
463
|
+
|
|
464
|
+
@staticmethod
|
|
465
|
+
@check_arguments
|
|
466
|
+
def datetime_to_string_for_logfile_name(datetime_object: datetime, add_milliseconds: bool ) -> str:
|
|
467
|
+
"""returns a string like '2025-08-21T15-30-00+02-00' or '2025-08-21T15-30-00.123+02-00' depending on the value of add_milliseconds"""
|
|
468
|
+
return GeneralUtilities.datetime_to_string_base(datetime_object, add_milliseconds, "T", "-", False)
|
|
469
|
+
|
|
470
|
+
@staticmethod
|
|
471
|
+
@check_arguments
|
|
472
|
+
def datetime_to_string_for_logfile_entry(datetime_object: datetime, add_milliseconds: bool ) -> str:
|
|
473
|
+
"""returns a string like '2025-08-21T15:30:00+02:00' or '2025-08-21T15:30:00.123+02:00' depending on the value of add_milliseconds"""
|
|
474
|
+
return GeneralUtilities.datetime_to_string_base(datetime_object, add_milliseconds, "T", ":", False)
|
|
475
|
+
|
|
476
|
+
@staticmethod
|
|
477
|
+
@check_arguments
|
|
478
|
+
def datetime_to_string_for_readable_entry(datetime_object: datetime, add_milliseconds: bool ) -> str:
|
|
479
|
+
"""returns a string like '2025-08-21 15-30-00 +02:00' or '2025-08-21 15:30:00.123 +02:00' depending on the value of add_milliseconds"""
|
|
480
|
+
return GeneralUtilities.datetime_to_string_base(datetime_object, add_milliseconds, " ", ":", True)
|
|
481
|
+
|
|
482
|
+
@staticmethod
|
|
483
|
+
@check_arguments
|
|
484
|
+
def datetime_to_string_base(datetime_object: datetime, add_milliseconds: bool ,mid_separator:str,time_separator:str,add_whitespace_before_timezone:bool) -> str:
|
|
485
|
+
dt:datetime=None
|
|
486
|
+
if datetime_object.tzinfo is None:
|
|
487
|
+
dt = datetime_object.replace(tzinfo=timezone.utc) # assume UTC when no timezone is given
|
|
488
|
+
else:
|
|
489
|
+
dt = datetime_object
|
|
490
|
+
pattern = "%Y-%m-%d"+mid_separator+"%H"+time_separator+"%M"+time_separator+"%S"
|
|
491
|
+
if add_milliseconds:
|
|
492
|
+
pattern = pattern+".%f"
|
|
493
|
+
if add_whitespace_before_timezone:
|
|
494
|
+
pattern = pattern+" "
|
|
495
|
+
pattern = pattern+"%z"
|
|
496
|
+
|
|
497
|
+
result = dt.strftime(pattern)
|
|
498
|
+
result = result[:-2] + time_separator + result[-2:]
|
|
499
|
+
return result
|
|
500
|
+
|
|
501
|
+
@staticmethod
|
|
502
|
+
@check_arguments
|
|
503
|
+
def string_has_nonwhitespace_content(string: str) -> bool:
|
|
504
|
+
if string is None:
|
|
505
|
+
return False
|
|
506
|
+
else:
|
|
507
|
+
return len(string.strip()) > 0
|
|
508
|
+
|
|
509
|
+
@staticmethod
|
|
510
|
+
@check_arguments
|
|
511
|
+
def string_is_none_or_empty(argument: str) -> bool:
|
|
512
|
+
if argument is None:
|
|
513
|
+
return True
|
|
514
|
+
type_of_argument = type(argument)
|
|
515
|
+
if type_of_argument == str:
|
|
516
|
+
return argument == GeneralUtilities.empty_string
|
|
517
|
+
else:
|
|
518
|
+
raise ValueError(f"expected string-variable in argument of string_is_none_or_empty but the type was '{str(type_of_argument)}'")
|
|
519
|
+
|
|
520
|
+
@staticmethod
|
|
521
|
+
@check_arguments
|
|
522
|
+
def string_is_none_or_whitespace(string: str) -> bool:
|
|
523
|
+
if GeneralUtilities.string_is_none_or_empty(string):
|
|
524
|
+
return True
|
|
525
|
+
else:
|
|
526
|
+
return string.strip() == GeneralUtilities.empty_string
|
|
527
|
+
|
|
528
|
+
@staticmethod
|
|
529
|
+
@check_arguments
|
|
530
|
+
def strip_new_line_character(value: str) -> str:
|
|
531
|
+
while not GeneralUtilities.__strip_new_line_character_helper_value_is_ok(value):
|
|
532
|
+
value = GeneralUtilities.__strip_new_line_character_helper_normalize_value(value)
|
|
533
|
+
return value
|
|
534
|
+
|
|
535
|
+
@staticmethod
|
|
536
|
+
@check_arguments
|
|
537
|
+
def __strip_new_line_character_helper_value_is_ok(value: str) -> bool:
|
|
538
|
+
if value.startswith("\r") or value.endswith("\r"):
|
|
539
|
+
return False
|
|
540
|
+
if value.startswith("\n") or value.endswith("\n"):
|
|
541
|
+
return False
|
|
542
|
+
return True
|
|
543
|
+
|
|
544
|
+
@staticmethod
|
|
545
|
+
@check_arguments
|
|
546
|
+
def __strip_new_line_character_helper_normalize_value(value: str) -> str:
|
|
547
|
+
return value.strip('\n').strip('\r')
|
|
548
|
+
|
|
549
|
+
@staticmethod
|
|
550
|
+
@check_arguments
|
|
551
|
+
def file_ends_with_newline(file: str) -> bool:
|
|
552
|
+
file=GeneralUtilities.normalize_path(file)
|
|
553
|
+
with open(file, "rb") as file_object:
|
|
554
|
+
return GeneralUtilities.ends_with_newline_character(file_object.read())
|
|
555
|
+
|
|
556
|
+
@staticmethod
|
|
557
|
+
@check_arguments
|
|
558
|
+
def ends_with_newline_character(content: bytes) -> bool:
|
|
559
|
+
result = content.endswith(GeneralUtilities.string_to_bytes("\n"))
|
|
560
|
+
return result
|
|
561
|
+
|
|
562
|
+
@staticmethod
|
|
563
|
+
@check_arguments
|
|
564
|
+
def file_ends_with_content(file: str) -> bool:
|
|
565
|
+
content = GeneralUtilities.read_binary_from_file(file)
|
|
566
|
+
if len(content) == 0:
|
|
567
|
+
return False
|
|
568
|
+
else:
|
|
569
|
+
if GeneralUtilities.ends_with_newline_character(content):
|
|
570
|
+
return False
|
|
571
|
+
else:
|
|
572
|
+
return True
|
|
573
|
+
|
|
574
|
+
@staticmethod
|
|
575
|
+
@check_arguments
|
|
576
|
+
def get_new_line_character_for_textfile_if_required(file: str) -> bool:
|
|
577
|
+
if GeneralUtilities.file_ends_with_content(file):
|
|
578
|
+
return "\n"
|
|
579
|
+
else:
|
|
580
|
+
return GeneralUtilities.empty_string
|
|
581
|
+
|
|
582
|
+
@staticmethod
|
|
583
|
+
@check_arguments
|
|
584
|
+
def append_line_to_file(file: str, line: str, encoding: str = "utf-8") -> None:
|
|
585
|
+
file=GeneralUtilities.normalize_path(file)
|
|
586
|
+
GeneralUtilities.append_lines_to_file(file, [line], encoding)
|
|
587
|
+
|
|
588
|
+
@staticmethod
|
|
589
|
+
@check_arguments
|
|
590
|
+
def append_lines_to_file(file: str, lines: list[str], encoding: str = "utf-8") -> None:
|
|
591
|
+
file=GeneralUtilities.normalize_path(file)
|
|
592
|
+
if len(lines) == 0:
|
|
593
|
+
return
|
|
594
|
+
is_first_line = True
|
|
595
|
+
for line in lines:
|
|
596
|
+
insert_linebreak: bool
|
|
597
|
+
if is_first_line:
|
|
598
|
+
insert_linebreak = GeneralUtilities.file_ends_with_content(file)
|
|
599
|
+
else:
|
|
600
|
+
insert_linebreak = True
|
|
601
|
+
line_to_write: str = None
|
|
602
|
+
if insert_linebreak:
|
|
603
|
+
line_to_write = "\n"+line
|
|
604
|
+
else:
|
|
605
|
+
line_to_write = line
|
|
606
|
+
with open(file, "r+b") as fileObject:
|
|
607
|
+
fileObject.seek(0, os.SEEK_END)
|
|
608
|
+
fileObject.write(GeneralUtilities.string_to_bytes(line_to_write, encoding))
|
|
609
|
+
is_first_line = False
|
|
610
|
+
|
|
611
|
+
@staticmethod
|
|
612
|
+
@check_arguments
|
|
613
|
+
def append_to_file(file: str, content: str, encoding: str = "utf-8") -> None:
|
|
614
|
+
file=GeneralUtilities.normalize_path(file)
|
|
615
|
+
GeneralUtilities.assert_condition(not "\n" in content, "Appending multiple lines is not allowed. Use append_lines_to_file instead.")
|
|
616
|
+
with open(file, "a", encoding=encoding) as fileObject:
|
|
617
|
+
fileObject.write(content)
|
|
618
|
+
|
|
619
|
+
@staticmethod
|
|
620
|
+
@check_arguments
|
|
621
|
+
def ensure_directory_exists(path: str) -> None:
|
|
622
|
+
path=GeneralUtilities.normalize_path(path)
|
|
623
|
+
if not os.path.isdir(path):
|
|
624
|
+
os.makedirs(path)
|
|
625
|
+
|
|
626
|
+
@staticmethod
|
|
627
|
+
@check_arguments
|
|
628
|
+
def ensure_file_exists(path: str) -> None:
|
|
629
|
+
path=GeneralUtilities.normalize_path(path)
|
|
630
|
+
if (not os.path.isfile(path)):
|
|
631
|
+
with open(path, "a+", encoding="utf-8"):
|
|
632
|
+
pass
|
|
633
|
+
|
|
634
|
+
@staticmethod
|
|
635
|
+
@check_arguments
|
|
636
|
+
def __remove_readonly(func, path, _):
|
|
637
|
+
os.chmod(path, stat.S_IWRITE)
|
|
638
|
+
func(path)
|
|
639
|
+
|
|
640
|
+
@staticmethod
|
|
641
|
+
@check_arguments
|
|
642
|
+
def __rmtree(directory: str) -> None:
|
|
643
|
+
shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly) # pylint: disable=deprecated-argument
|
|
644
|
+
|
|
645
|
+
@staticmethod
|
|
646
|
+
@check_arguments
|
|
647
|
+
def ensure_directory_does_not_exist(path: str) -> None:
|
|
648
|
+
if (os.path.isdir(path)):
|
|
649
|
+
for root, dirs, files in os.walk(path, topdown=False):
|
|
650
|
+
for name in files:
|
|
651
|
+
filename = os.path.join(root, name)
|
|
652
|
+
os.chmod(filename, stat.S_IWUSR)
|
|
653
|
+
os.remove(filename)
|
|
654
|
+
for name in dirs:
|
|
655
|
+
GeneralUtilities.__rmtree(os.path.join(root, name))
|
|
656
|
+
GeneralUtilities.__rmtree(path)
|
|
657
|
+
|
|
658
|
+
@staticmethod
|
|
659
|
+
@check_arguments
|
|
660
|
+
def ensure_folder_exists_and_is_empty(path: str) -> None:
|
|
661
|
+
GeneralUtilities.ensure_directory_exists(path)
|
|
662
|
+
for filename in os.listdir(path):
|
|
663
|
+
file_path = os.path.join(path, filename)
|
|
664
|
+
if os.path.isfile(file_path):
|
|
665
|
+
os.remove(file_path)
|
|
666
|
+
if os.path.islink(file_path):
|
|
667
|
+
os.unlink(file_path)
|
|
668
|
+
elif os.path.isdir(file_path):
|
|
669
|
+
shutil.rmtree(file_path)
|
|
670
|
+
|
|
671
|
+
@staticmethod
|
|
672
|
+
@check_arguments
|
|
673
|
+
def ensure_file_does_not_exist(path: str) -> None:
|
|
674
|
+
if (os.path.isfile(path)):
|
|
675
|
+
os.remove(path)
|
|
676
|
+
|
|
677
|
+
@staticmethod
|
|
678
|
+
@check_arguments
|
|
679
|
+
def ensure_path_does_not_exist(path: str) -> None:
|
|
680
|
+
if (os.path.isfile(path)):
|
|
681
|
+
GeneralUtilities.ensure_file_does_not_exist(path)
|
|
682
|
+
if (os.path.isdir(path)):
|
|
683
|
+
GeneralUtilities.ensure_directory_does_not_exist(path)
|
|
684
|
+
|
|
685
|
+
@staticmethod
|
|
686
|
+
@check_arguments
|
|
687
|
+
def format_xml_file(filepath: str) -> None:
|
|
688
|
+
GeneralUtilities.format_xml_file_with_encoding(filepath, "utf-8")
|
|
689
|
+
|
|
690
|
+
@staticmethod
|
|
691
|
+
@check_arguments
|
|
692
|
+
def format_xml_file_with_encoding(filepath: str, encoding: str) -> None:
|
|
693
|
+
with open(filepath, 'r', encoding=encoding) as file:
|
|
694
|
+
text = file.read()
|
|
695
|
+
text = parse(text).toprettyxml()
|
|
696
|
+
with open(filepath, 'w', encoding=encoding) as file:
|
|
697
|
+
file.write(text)
|
|
698
|
+
|
|
699
|
+
@staticmethod
|
|
700
|
+
@check_arguments
|
|
701
|
+
def get_clusters_and_sectors_of_disk(diskpath: str) -> None:
|
|
702
|
+
GeneralUtilities.assert_condition(GeneralUtilities.current_system_is_windows(), "get_clusters_and_sectors_of_disk(diskpath) is only available on windows.")
|
|
703
|
+
sectorsPerCluster = ctypes.c_ulonglong(0)
|
|
704
|
+
bytesPerSector = ctypes.c_ulonglong(0)
|
|
705
|
+
rootPathName = ctypes.c_wchar_p(diskpath)
|
|
706
|
+
ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, ctypes.pointer(sectorsPerCluster), ctypes.pointer(bytesPerSector), None, None)
|
|
707
|
+
return (sectorsPerCluster.value, bytesPerSector.value)
|
|
708
|
+
|
|
709
|
+
@staticmethod
|
|
710
|
+
@check_arguments
|
|
711
|
+
def ensure_path_is_not_quoted(path: str) -> str:
|
|
712
|
+
if (path.startswith("\"") and path.endswith("\"")) or (path.startswith("'") and path.endswith("'")):
|
|
713
|
+
path = path[1:]
|
|
714
|
+
path = path[:-1]
|
|
715
|
+
return path
|
|
716
|
+
else:
|
|
717
|
+
return path
|
|
718
|
+
|
|
719
|
+
@staticmethod
|
|
720
|
+
@check_arguments
|
|
721
|
+
def get_missing_files(folderA: str, folderB: str) -> list:
|
|
722
|
+
folderA_length = len(folderA)
|
|
723
|
+
result = []
|
|
724
|
+
for fileA in GeneralUtilities.absolute_file_paths(folderA):
|
|
725
|
+
file = fileA[folderA_length:]
|
|
726
|
+
fileB = folderB + file
|
|
727
|
+
if not os.path.isfile(fileB):
|
|
728
|
+
result.append(fileB)
|
|
729
|
+
return result
|
|
730
|
+
|
|
731
|
+
@staticmethod
|
|
732
|
+
@check_arguments
|
|
733
|
+
def to_pascal_case(s: str) -> str:
|
|
734
|
+
return ''.join(current.lower() if prev.isalnum() else current.upper() for prev, current in zip(' ' + s, s) if current.isalnum())
|
|
735
|
+
|
|
736
|
+
@staticmethod
|
|
737
|
+
@check_arguments
|
|
738
|
+
def to_snake_case(s: str) -> str:
|
|
739
|
+
return '_'.join(t.lower() for t in re.split(r'[^a-zA-Z0-9]+', s) if t)
|
|
740
|
+
|
|
741
|
+
@staticmethod
|
|
742
|
+
@check_arguments
|
|
743
|
+
def to_camel_case(s: str) -> str:
|
|
744
|
+
words = [t.lower() for t in re.split(r'[^a-zA-Z0-9]+', s) if t]
|
|
745
|
+
return words[0] + ''.join(w.capitalize() for w in words[1:]) if words else ''
|
|
746
|
+
|
|
747
|
+
@staticmethod
|
|
748
|
+
@check_arguments
|
|
749
|
+
def to_kebab_case(s: str) -> str:
|
|
750
|
+
return '-'.join(t.lower() for t in re.split(r'[^a-zA-Z0-9]+', s) if t)
|
|
751
|
+
|
|
752
|
+
@staticmethod
|
|
753
|
+
@check_arguments
|
|
754
|
+
def find_between(s: str, start: str, end: str) -> str:
|
|
755
|
+
return s.split(start)[1].split(end)[0]
|
|
756
|
+
|
|
757
|
+
@staticmethod
|
|
758
|
+
@check_arguments
|
|
759
|
+
def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None:
|
|
760
|
+
lines = [GeneralUtilities.strip_new_line_character(line) for line in lines]
|
|
761
|
+
content = "\n".join(lines)
|
|
762
|
+
GeneralUtilities.write_text_to_file(file, content, encoding)
|
|
763
|
+
|
|
764
|
+
@staticmethod
|
|
765
|
+
@check_arguments
|
|
766
|
+
def write_text_to_file(file: str, content: str, encoding="utf-8") -> None:
|
|
767
|
+
GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding)))
|
|
768
|
+
|
|
769
|
+
@staticmethod
|
|
770
|
+
@check_arguments
|
|
771
|
+
def write_binary_to_file(file: str, content: bytes) -> None:
|
|
772
|
+
with open(file, "wb") as file_object:
|
|
773
|
+
file_object.write(content)
|
|
774
|
+
|
|
775
|
+
@staticmethod
|
|
776
|
+
def is_binary_file(path: str):
|
|
777
|
+
content = GeneralUtilities.read_binary_from_file(path)
|
|
778
|
+
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']
|
|
779
|
+
for binary_content_indicator in binary_content_indicators:
|
|
780
|
+
if binary_content_indicator in content:
|
|
781
|
+
return True
|
|
782
|
+
return False
|
|
783
|
+
|
|
784
|
+
@staticmethod
|
|
785
|
+
@check_arguments
|
|
786
|
+
def read_lines_from_file(file: str, encoding="utf-8") -> list[str]:
|
|
787
|
+
content = GeneralUtilities.read_text_from_file(file, encoding)
|
|
788
|
+
if len(content) == 0:
|
|
789
|
+
return []
|
|
790
|
+
else:
|
|
791
|
+
return [GeneralUtilities.strip_new_line_character(line) for line in content.split('\n')]
|
|
792
|
+
|
|
793
|
+
@staticmethod
|
|
794
|
+
@check_arguments
|
|
795
|
+
def read_nonempty_lines_from_file(file: str, encoding="utf-8") -> list[str]:
|
|
796
|
+
return [line for line in GeneralUtilities.read_lines_from_file(file, encoding) if GeneralUtilities.string_has_content(line)]
|
|
797
|
+
|
|
798
|
+
@staticmethod
|
|
799
|
+
@check_arguments
|
|
800
|
+
def read_text_from_file(file: str, encoding="utf-8") -> str:
|
|
801
|
+
GeneralUtilities.assert_file_exists(file)
|
|
802
|
+
return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding)
|
|
803
|
+
|
|
804
|
+
@staticmethod
|
|
805
|
+
@check_arguments
|
|
806
|
+
def read_text_from_file_without_linebreak(file: str, encoding="utf-8") -> str:
|
|
807
|
+
return GeneralUtilities.read_text_from_file(file,encoding).replace("\n","").replace("\r","")
|
|
808
|
+
|
|
809
|
+
@staticmethod
|
|
810
|
+
@check_arguments
|
|
811
|
+
def read_binary_from_file(file: str) -> bytes:
|
|
812
|
+
file=GeneralUtilities.normalize_path(file)
|
|
813
|
+
with open(file, "rb") as file_object:
|
|
814
|
+
return file_object.read()
|
|
815
|
+
|
|
816
|
+
@staticmethod
|
|
817
|
+
@check_arguments
|
|
818
|
+
def timedelta_to_simple_string(delta: timedelta) -> str:
|
|
819
|
+
return (datetime(1970, 1, 1, 0, 0, 0) + delta).strftime('%H:%M:%S')
|
|
820
|
+
|
|
821
|
+
@staticmethod
|
|
822
|
+
@check_arguments
|
|
823
|
+
def resolve_relative_path_from_current_working_directory(path: str) -> str:
|
|
824
|
+
return GeneralUtilities.resolve_relative_path(path, os.getcwd())
|
|
825
|
+
|
|
826
|
+
@staticmethod
|
|
827
|
+
@check_arguments
|
|
828
|
+
def resolve_relative_path(path: str, base_path: str):
|
|
829
|
+
if (os.path.isabs(path)):
|
|
830
|
+
return path
|
|
831
|
+
else:
|
|
832
|
+
return str(Path(os.path.join(base_path, path)).resolve())
|
|
833
|
+
|
|
834
|
+
@staticmethod
|
|
835
|
+
@check_arguments
|
|
836
|
+
def get_metadata_for_file_for_clone_folder_structure(file: str) -> str:
|
|
837
|
+
size = os.path.getsize(file)
|
|
838
|
+
last_modified_timestamp = os.path.getmtime(file)
|
|
839
|
+
hash_value = GeneralUtilities.get_sha256_of_file(file)
|
|
840
|
+
last_access_timestamp = os.path.getatime(file)
|
|
841
|
+
return f'{{"size":"{size}","sha256":"{hash_value}","mtime":"{last_modified_timestamp}","atime":"{last_access_timestamp}"}}'
|
|
842
|
+
|
|
843
|
+
@staticmethod
|
|
844
|
+
@check_arguments
|
|
845
|
+
def clone_folder_structure(source: str, target: str, copy_only_metadata: bool):
|
|
846
|
+
source = GeneralUtilities.resolve_relative_path(source, os.getcwd())
|
|
847
|
+
target = GeneralUtilities.resolve_relative_path(target, os.getcwd())
|
|
848
|
+
length_of_source = len(source)
|
|
849
|
+
for source_file in GeneralUtilities.absolute_file_paths(source):
|
|
850
|
+
target_file = target+source_file[length_of_source:]
|
|
851
|
+
GeneralUtilities.ensure_directory_exists(os.path.dirname(target_file))
|
|
852
|
+
if copy_only_metadata:
|
|
853
|
+
with open(target_file, 'w', encoding='utf8') as f:
|
|
854
|
+
f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file))
|
|
855
|
+
else:
|
|
856
|
+
shutil.copyfile(source_file, target_file)
|
|
857
|
+
|
|
858
|
+
@staticmethod
|
|
859
|
+
@check_arguments
|
|
860
|
+
def current_user_has_elevated_privileges() -> bool:
|
|
861
|
+
try:
|
|
862
|
+
return os.getuid() == 0
|
|
863
|
+
except AttributeError:
|
|
864
|
+
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
|
865
|
+
|
|
866
|
+
@staticmethod
|
|
867
|
+
@check_arguments
|
|
868
|
+
def ensure_elevated_privileges() -> None:
|
|
869
|
+
if (not GeneralUtilities.current_user_has_elevated_privileges()):
|
|
870
|
+
raise ValueError("Not enough privileges.")
|
|
871
|
+
|
|
872
|
+
@staticmethod
|
|
873
|
+
@check_arguments
|
|
874
|
+
def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
875
|
+
for file in GeneralUtilities.get_direct_files_of_folder(folder):
|
|
876
|
+
GeneralUtilities.replace_in_filename(file, replace_from, replace_to, replace_only_full_match)
|
|
877
|
+
for sub_folder in GeneralUtilities.get_direct_folders_of_folder(folder):
|
|
878
|
+
GeneralUtilities.rename_names_of_all_files_and_folders(sub_folder, replace_from, replace_to, replace_only_full_match)
|
|
879
|
+
GeneralUtilities.replace_in_foldername(folder, replace_from, replace_to, replace_only_full_match)
|
|
880
|
+
|
|
881
|
+
@staticmethod
|
|
882
|
+
@check_arguments
|
|
883
|
+
def get_direct_files_of_folder(folder: str) -> list[str]:
|
|
884
|
+
result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))]
|
|
885
|
+
result = sorted(result, key=str.casefold)
|
|
886
|
+
return result
|
|
887
|
+
|
|
888
|
+
@staticmethod
|
|
889
|
+
@check_arguments
|
|
890
|
+
def get_direct_folders_of_folder(folder: str) -> list[str]:
|
|
891
|
+
result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
|
|
892
|
+
result = sorted(result, key=str.casefold)
|
|
893
|
+
return result
|
|
894
|
+
|
|
895
|
+
@staticmethod
|
|
896
|
+
@check_arguments
|
|
897
|
+
def get_all_files_of_folder(folder: str) -> list[str]:
|
|
898
|
+
result = list()
|
|
899
|
+
result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
|
|
900
|
+
for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
|
|
901
|
+
result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
|
|
902
|
+
result = sorted(result, key=str.casefold)
|
|
903
|
+
return result
|
|
904
|
+
|
|
905
|
+
@staticmethod
|
|
906
|
+
@check_arguments
|
|
907
|
+
def get_all_folders_of_folder(folder: str) -> list[str]:
|
|
908
|
+
result = list()
|
|
909
|
+
subfolders = GeneralUtilities.get_direct_folders_of_folder(folder)
|
|
910
|
+
result.extend(subfolders)
|
|
911
|
+
for subfolder in subfolders:
|
|
912
|
+
result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
|
|
913
|
+
result = sorted(result, key=str.casefold)
|
|
914
|
+
return result
|
|
915
|
+
|
|
916
|
+
@staticmethod
|
|
917
|
+
@check_arguments
|
|
918
|
+
def get_all_objects_of_folder(folder: str) -> list[str]:
|
|
919
|
+
return sorted(GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder), key=str.casefold)
|
|
920
|
+
|
|
921
|
+
@staticmethod
|
|
922
|
+
@check_arguments
|
|
923
|
+
def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
924
|
+
filename = Path(file).name
|
|
925
|
+
if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
|
|
926
|
+
folder_of_file = os.path.dirname(file)
|
|
927
|
+
os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
|
|
928
|
+
|
|
929
|
+
@staticmethod
|
|
930
|
+
@check_arguments
|
|
931
|
+
def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
932
|
+
foldername = Path(folder).name
|
|
933
|
+
if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
|
|
934
|
+
folder_of_folder = os.path.dirname(folder)
|
|
935
|
+
os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
|
|
936
|
+
|
|
937
|
+
@staticmethod
|
|
938
|
+
@check_arguments
|
|
939
|
+
def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool:
|
|
940
|
+
if replace_only_full_match:
|
|
941
|
+
return input_text == search_text
|
|
942
|
+
else:
|
|
943
|
+
return search_text in input_text
|
|
944
|
+
|
|
945
|
+
@staticmethod
|
|
946
|
+
@check_arguments
|
|
947
|
+
def str_none_safe(variable) -> str:
|
|
948
|
+
if variable is None:
|
|
949
|
+
return ''
|
|
950
|
+
else:
|
|
951
|
+
return str(variable)
|
|
952
|
+
|
|
953
|
+
@staticmethod
|
|
954
|
+
@check_arguments
|
|
955
|
+
def arguments_to_array(arguments_as_string: str) -> list[str]:
|
|
956
|
+
if arguments_as_string is None:
|
|
957
|
+
return []
|
|
958
|
+
if GeneralUtilities.string_has_content(arguments_as_string):
|
|
959
|
+
return arguments_as_string.split(" ") # TODO this function should get improved to allow whitespaces in quote-substrings
|
|
960
|
+
else:
|
|
961
|
+
return []
|
|
962
|
+
|
|
963
|
+
@staticmethod
|
|
964
|
+
@check_arguments
|
|
965
|
+
def arguments_to_array_for_log(arguments_as_string: str) -> list[str]:
|
|
966
|
+
if arguments_as_string is None:
|
|
967
|
+
return None
|
|
968
|
+
return GeneralUtilities.arguments_to_array(arguments_as_string)
|
|
969
|
+
|
|
970
|
+
@staticmethod
|
|
971
|
+
@check_arguments
|
|
972
|
+
def get_sha256_of_file(file: str) -> str:
|
|
973
|
+
sha256 = hashlib.sha256()
|
|
974
|
+
with open(file, "rb") as fileObject:
|
|
975
|
+
for chunk in iter(lambda: fileObject.read(4096), b""):
|
|
976
|
+
sha256.update(chunk)
|
|
977
|
+
return sha256.hexdigest()
|
|
978
|
+
|
|
979
|
+
@staticmethod
|
|
980
|
+
@check_arguments
|
|
981
|
+
def remove_duplicates(input_list) -> list:
|
|
982
|
+
result = []
|
|
983
|
+
for item in input_list:
|
|
984
|
+
if not item in result:
|
|
985
|
+
result.append(item)
|
|
986
|
+
return result
|
|
987
|
+
|
|
988
|
+
@staticmethod
|
|
989
|
+
@check_arguments
|
|
990
|
+
def print_stacktrace() -> None:
|
|
991
|
+
for line in traceback.format_stack():
|
|
992
|
+
GeneralUtilities.write_message_to_stderr(line.strip())
|
|
993
|
+
|
|
994
|
+
@staticmethod
|
|
995
|
+
@check_arguments
|
|
996
|
+
def string_to_boolean(value: str) -> bool:
|
|
997
|
+
value = value.strip().lower()
|
|
998
|
+
if value in ('yes', 'y', 'true', 't', '1'):
|
|
999
|
+
return True
|
|
1000
|
+
elif value in ('no', 'n', 'false', 'f', '0'):
|
|
1001
|
+
return False
|
|
1002
|
+
else:
|
|
1003
|
+
raise ValueError(f"Can not convert '{value}' to a boolean value")
|
|
1004
|
+
|
|
1005
|
+
@staticmethod
|
|
1006
|
+
@check_arguments
|
|
1007
|
+
def file_is_empty(file: str) -> bool:
|
|
1008
|
+
return os.stat(file).st_size == 0
|
|
1009
|
+
|
|
1010
|
+
@staticmethod
|
|
1011
|
+
@check_arguments
|
|
1012
|
+
def folder_is_empty(folder: str) -> bool:
|
|
1013
|
+
return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders_of_folder(folder)) == 0
|
|
1014
|
+
|
|
1015
|
+
@staticmethod
|
|
1016
|
+
@check_arguments
|
|
1017
|
+
def get_time_based_logfile_by_folder(folder: str, name: str = "Log") -> str:
|
|
1018
|
+
return os.path.join(GeneralUtilities.resolve_relative_path_from_current_working_directory(folder), f"{GeneralUtilities.get_time_based_logfilename(name)}.log")
|
|
1019
|
+
|
|
1020
|
+
@staticmethod
|
|
1021
|
+
@check_arguments
|
|
1022
|
+
def get_now() -> datetime:
|
|
1023
|
+
return datetime.now().astimezone().replace(microsecond=0)
|
|
1024
|
+
|
|
1025
|
+
@staticmethod
|
|
1026
|
+
@check_arguments
|
|
1027
|
+
def get_time_based_logfilename(name: str = "Log") -> str:
|
|
1028
|
+
d = GeneralUtilities.get_now()
|
|
1029
|
+
return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d,False)}"
|
|
1030
|
+
|
|
1031
|
+
@staticmethod
|
|
1032
|
+
@check_arguments
|
|
1033
|
+
def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str:
|
|
1034
|
+
return payload.decode(encoding, errors="ignore")
|
|
1035
|
+
|
|
1036
|
+
@staticmethod
|
|
1037
|
+
@check_arguments
|
|
1038
|
+
def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes:
|
|
1039
|
+
return payload.encode(encoding, errors="ignore")
|
|
1040
|
+
|
|
1041
|
+
@staticmethod
|
|
1042
|
+
@check_arguments
|
|
1043
|
+
def contains_line(lines, regex: str) -> bool:
|
|
1044
|
+
for line in lines:
|
|
1045
|
+
if (re.match(regex, line)):
|
|
1046
|
+
return True
|
|
1047
|
+
return False
|
|
1048
|
+
|
|
1049
|
+
@staticmethod
|
|
1050
|
+
@check_arguments
|
|
1051
|
+
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]]:
|
|
1052
|
+
lines = GeneralUtilities.read_lines_from_file(file, encoding)
|
|
1053
|
+
|
|
1054
|
+
if ignore_first_line:
|
|
1055
|
+
lines = lines[1:]
|
|
1056
|
+
result = list()
|
|
1057
|
+
line: str
|
|
1058
|
+
for line_loopvariable in lines:
|
|
1059
|
+
use_line = True
|
|
1060
|
+
line = line_loopvariable
|
|
1061
|
+
|
|
1062
|
+
if trim_values:
|
|
1063
|
+
line = line.strip()
|
|
1064
|
+
if ignore_empty_lines:
|
|
1065
|
+
if not GeneralUtilities.string_has_content(line):
|
|
1066
|
+
use_line = False
|
|
1067
|
+
|
|
1068
|
+
if treat_number_sign_at_begin_of_line_as_comment:
|
|
1069
|
+
if line.startswith("#"):
|
|
1070
|
+
use_line = False
|
|
1071
|
+
|
|
1072
|
+
if use_line:
|
|
1073
|
+
if separator_character in line:
|
|
1074
|
+
raw_values_of_line = GeneralUtilities.to_list(line, separator_character)
|
|
1075
|
+
else:
|
|
1076
|
+
raw_values_of_line = [line]
|
|
1077
|
+
if trim_values:
|
|
1078
|
+
raw_values_of_line = [value.strip() for value in raw_values_of_line]
|
|
1079
|
+
values_of_line = []
|
|
1080
|
+
for raw_value_of_line in raw_values_of_line:
|
|
1081
|
+
value_of_line = raw_value_of_line
|
|
1082
|
+
if values_are_surrounded_by_quotes:
|
|
1083
|
+
value_of_line = value_of_line[1:]
|
|
1084
|
+
value_of_line = value_of_line[:-1]
|
|
1085
|
+
value_of_line = value_of_line.replace('""', '"')
|
|
1086
|
+
values_of_line.append(value_of_line)
|
|
1087
|
+
result.extend([values_of_line])
|
|
1088
|
+
return result
|
|
1089
|
+
|
|
1090
|
+
@staticmethod
|
|
1091
|
+
@check_arguments
|
|
1092
|
+
def epew_is_available() -> bool:
|
|
1093
|
+
return GeneralUtilities.tool_is_available("epew")
|
|
1094
|
+
|
|
1095
|
+
@staticmethod
|
|
1096
|
+
@check_arguments
|
|
1097
|
+
def tool_is_available(toolname: str) -> bool:
|
|
1098
|
+
try:
|
|
1099
|
+
return shutil.which(toolname) is not None
|
|
1100
|
+
except:
|
|
1101
|
+
return False
|
|
1102
|
+
|
|
1103
|
+
@staticmethod
|
|
1104
|
+
@check_arguments
|
|
1105
|
+
@deprecated
|
|
1106
|
+
def absolute_file_paths(directory: str) -> list[str]:
|
|
1107
|
+
return GeneralUtilities.get_all_files_of_folder(directory)
|
|
1108
|
+
|
|
1109
|
+
@staticmethod
|
|
1110
|
+
@check_arguments
|
|
1111
|
+
def to_list(list_as_string: str, separator: str = ",") -> list[str]:
|
|
1112
|
+
result = list()
|
|
1113
|
+
if list_as_string is not None:
|
|
1114
|
+
list_as_string = list_as_string.strip()
|
|
1115
|
+
if list_as_string == GeneralUtilities.empty_string:
|
|
1116
|
+
pass
|
|
1117
|
+
elif separator in list_as_string:
|
|
1118
|
+
for item in list_as_string.split(separator):
|
|
1119
|
+
result.append(item.strip())
|
|
1120
|
+
else:
|
|
1121
|
+
result.append(list_as_string)
|
|
1122
|
+
return result
|
|
1123
|
+
|
|
1124
|
+
@staticmethod
|
|
1125
|
+
@check_arguments
|
|
1126
|
+
def get_next_square_number(number: int) -> int:
|
|
1127
|
+
GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative numbers")
|
|
1128
|
+
if number == 0:
|
|
1129
|
+
return 1
|
|
1130
|
+
root = 0
|
|
1131
|
+
square = 0
|
|
1132
|
+
while square < number:
|
|
1133
|
+
root = root+1
|
|
1134
|
+
square = root*root
|
|
1135
|
+
return root*root
|
|
1136
|
+
|
|
1137
|
+
@staticmethod
|
|
1138
|
+
@check_arguments
|
|
1139
|
+
def generate_password(length: int = 16, alphabet: str = None) -> None:
|
|
1140
|
+
if alphabet is None:
|
|
1141
|
+
alphabet = strin.ascii_letters + strin.digits+"_"
|
|
1142
|
+
return ''.join(secrets.choice(alphabet) for i in range(length))
|
|
1143
|
+
|
|
1144
|
+
@staticmethod
|
|
1145
|
+
@check_arguments
|
|
1146
|
+
def assert_condition(condition: bool, information: str = None) -> None:
|
|
1147
|
+
"""Throws an exception if the condition is false."""
|
|
1148
|
+
if (not condition):
|
|
1149
|
+
if information is None:
|
|
1150
|
+
information = "Internal assertion error."
|
|
1151
|
+
raise ValueError("Condition failed. "+information)
|
|
1152
|
+
|
|
1153
|
+
@staticmethod
|
|
1154
|
+
def current_system_is_windows():
|
|
1155
|
+
return platform.system() == 'Windows'
|
|
1156
|
+
|
|
1157
|
+
@staticmethod
|
|
1158
|
+
def current_system_is_linux():
|
|
1159
|
+
return platform.system() == 'Linux'
|
|
1160
|
+
|
|
1161
|
+
@staticmethod
|
|
1162
|
+
@check_arguments
|
|
1163
|
+
def get_line():
|
|
1164
|
+
return "--------------------------"
|
|
1165
|
+
|
|
1166
|
+
@staticmethod
|
|
1167
|
+
def get_longline():
|
|
1168
|
+
return GeneralUtilities.get_line() + GeneralUtilities.get_line()
|
|
1169
|
+
|
|
1170
|
+
@staticmethod
|
|
1171
|
+
@check_arguments
|
|
1172
|
+
def get_icon_check_empty(positive: bool) -> str:
|
|
1173
|
+
if positive:
|
|
1174
|
+
return "✅"
|
|
1175
|
+
else:
|
|
1176
|
+
return GeneralUtilities.empty_string
|
|
1177
|
+
|
|
1178
|
+
@staticmethod
|
|
1179
|
+
@check_arguments
|
|
1180
|
+
def get_icon_check_cross(positive: bool) -> str:
|
|
1181
|
+
if positive:
|
|
1182
|
+
return "✅"
|
|
1183
|
+
else:
|
|
1184
|
+
return "❌"
|
|
1185
|
+
|
|
1186
|
+
@staticmethod
|
|
1187
|
+
@check_arguments
|
|
1188
|
+
def get_certificate_expiry_date(certificate_file: str) -> datetime:
|
|
1189
|
+
with open(certificate_file, encoding="utf-8") as certificate_file_content:
|
|
1190
|
+
cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate_file_content.read())
|
|
1191
|
+
date_as_bytes = cert.get_notAfter()
|
|
1192
|
+
date_as_string = date_as_bytes.decode("utf-8")
|
|
1193
|
+
result = datetime.strptime(date_as_string, '%Y%m%d%H%M%SZ')
|
|
1194
|
+
return result
|
|
1195
|
+
|
|
1196
|
+
@staticmethod
|
|
1197
|
+
@check_arguments
|
|
1198
|
+
def certificate_is_expired(certificate_file: str) -> bool:
|
|
1199
|
+
return GeneralUtilities.get_certificate_expiry_date(certificate_file) < GeneralUtilities.get_now()
|
|
1200
|
+
|
|
1201
|
+
@staticmethod
|
|
1202
|
+
@check_arguments
|
|
1203
|
+
def internet_connection_is_available() -> bool:
|
|
1204
|
+
# TODO add more hosts to check to return true if at least one is available
|
|
1205
|
+
try:
|
|
1206
|
+
with urllib.request.urlopen("https://www.google.com") as url_result:
|
|
1207
|
+
return (url_result.code // 100) == 2
|
|
1208
|
+
except:
|
|
1209
|
+
pass
|
|
1210
|
+
return False
|
|
1211
|
+
|
|
1212
|
+
@staticmethod
|
|
1213
|
+
@check_arguments
|
|
1214
|
+
def replace_variable_in_string(input_string: str, variable_name: str, variable_value: str) -> str:
|
|
1215
|
+
GeneralUtilities.assert_condition(not "__" in variable_name, f"'{variable_name}' is an invalid variable name because it contains '__' which is treated as control-sequence.")
|
|
1216
|
+
return input_string.replace(f"__[{variable_name}]__", variable_value)
|
|
1217
|
+
|
|
1218
|
+
@staticmethod
|
|
1219
|
+
@check_arguments
|
|
1220
|
+
def replace_variable(prefix:str, variable_name:str, suffix:str, value:str, content: str) -> str:
|
|
1221
|
+
GeneralUtilities.assert_condition(not "__" in variable_name, f"'{variable_name}' is an invalid variable name because it contains '__' which is treated as control-sequence.")
|
|
1222
|
+
pattern = re.escape(GeneralUtilities.str_none_safe( prefix)) + r"\s*" +"__"+ re.escape(variable_name) + "__"+r"\s*" + re.escape(GeneralUtilities.str_none_safe( suffix))
|
|
1223
|
+
result:str= re.sub(pattern, value, content)
|
|
1224
|
+
GeneralUtilities.assert_condition(not f"__{variable_name}__" in result, f"Variable '{variable_name}' was not replaced in the content. This is likely caused by an error in the content or the variable name.")
|
|
1225
|
+
return result
|
|
1226
|
+
|
|
1227
|
+
@staticmethod
|
|
1228
|
+
@check_arguments
|
|
1229
|
+
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.
|
|
1230
|
+
GeneralUtilities.write_message_to_stdout(prompt)
|
|
1231
|
+
result: str = input()
|
|
1232
|
+
if print_result:
|
|
1233
|
+
GeneralUtilities.write_message_to_stdout(f"Result: {result}")
|
|
1234
|
+
return result
|
|
1235
|
+
|
|
1236
|
+
@staticmethod
|
|
1237
|
+
@check_arguments
|
|
1238
|
+
def run_program_simple(program: str, arguments: list[str], cwd: str = None) -> tuple[int, str, str]:
|
|
1239
|
+
if cwd is None:
|
|
1240
|
+
cwd = os.getcwd()
|
|
1241
|
+
cmd = [program]+arguments
|
|
1242
|
+
with subprocess.Popen(cmd, cwd=cwd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as process:
|
|
1243
|
+
stdout, stderr = process.communicate()
|
|
1244
|
+
exit_code = process.wait()
|
|
1245
|
+
return (exit_code, stdout, stderr)
|
|
1246
|
+
|
|
1247
|
+
@staticmethod
|
|
1248
|
+
@check_arguments
|
|
1249
|
+
def assert_file_exists(file: str,message=None) -> None:
|
|
1250
|
+
if message is None:
|
|
1251
|
+
message=f"File '{file}' does not exist."
|
|
1252
|
+
GeneralUtilities.assert_condition(os.path.isfile(file), message)
|
|
1253
|
+
|
|
1254
|
+
@staticmethod
|
|
1255
|
+
@check_arguments
|
|
1256
|
+
def assert_file_does_not_exist(file: str,message=None) -> None:
|
|
1257
|
+
if message is None:
|
|
1258
|
+
message=f"File '{file}' exists."
|
|
1259
|
+
GeneralUtilities.assert_condition(not os.path.isfile(file), message)
|
|
1260
|
+
|
|
1261
|
+
@staticmethod
|
|
1262
|
+
@check_arguments
|
|
1263
|
+
def assert_folder_exists(folder: str,message=None) -> None:
|
|
1264
|
+
if message is None:
|
|
1265
|
+
message=f"Folder '{folder}' does not exist."
|
|
1266
|
+
GeneralUtilities.assert_condition(os.path.isdir(folder),message )
|
|
1267
|
+
|
|
1268
|
+
@staticmethod
|
|
1269
|
+
@check_arguments
|
|
1270
|
+
def assert_folder_does_not_exist(folder: str,message=None) -> None:
|
|
1271
|
+
if message is None:
|
|
1272
|
+
message= f"Folder '{folder}' exists."
|
|
1273
|
+
GeneralUtilities.assert_condition(not os.path.isdir(folder), f"Folder '{folder}' exists.")
|
|
1274
|
+
|
|
1275
|
+
@staticmethod
|
|
1276
|
+
@check_arguments
|
|
1277
|
+
def assert_not_null(obj,message:str=None) -> str:
|
|
1278
|
+
if message is None:
|
|
1279
|
+
message="Variable is not set"
|
|
1280
|
+
GeneralUtilities.assert_condition(obj is not None, message)
|
|
1281
|
+
|
|
1282
|
+
@staticmethod
|
|
1283
|
+
@check_arguments
|
|
1284
|
+
def retry_action(action, amount_of_attempts: int, action_name: str = None,delay_in_seconds:int=2) -> None:
|
|
1285
|
+
amount_of_fails = 0
|
|
1286
|
+
last_exception:Exception=None
|
|
1287
|
+
GeneralUtilities.assert_condition(0<amount_of_attempts,"amount_of_attempts must be greater than 0.")
|
|
1288
|
+
while amount_of_fails<amount_of_attempts:
|
|
1289
|
+
try:
|
|
1290
|
+
result = action()
|
|
1291
|
+
return result
|
|
1292
|
+
except Exception as e:
|
|
1293
|
+
time.sleep(delay_in_seconds)
|
|
1294
|
+
amount_of_fails = amount_of_fails+1
|
|
1295
|
+
last_exception=e
|
|
1296
|
+
GeneralUtilities.assert_not_null(last_exception)
|
|
1297
|
+
message = "Action"
|
|
1298
|
+
if action_name is not None:
|
|
1299
|
+
message = f"{message} \"{action_name}\""
|
|
1300
|
+
message = f"{message} failed {amount_of_attempts} time(s)."
|
|
1301
|
+
GeneralUtilities.write_message_to_stderr(message)
|
|
1302
|
+
raise last_exception
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
@staticmethod
|
|
1306
|
+
@check_arguments
|
|
1307
|
+
def normalize_path(path)->str:
|
|
1308
|
+
path=str(path)
|
|
1309
|
+
if GeneralUtilities.current_system_is_windows():
|
|
1310
|
+
path=path.replace("/","\\")
|
|
1311
|
+
else:
|
|
1312
|
+
path=path.replace("\\","/")
|
|
1313
|
+
return path
|
|
1314
|
+
|
|
1315
|
+
@staticmethod
|
|
1316
|
+
@check_arguments
|
|
1317
|
+
def int_to_string(number: int, leading_zeroplaces: int, trailing_zeroplaces: int) -> str:
|
|
1318
|
+
return GeneralUtilities.float_to_string(float(number), leading_zeroplaces, trailing_zeroplaces)
|
|
1319
|
+
|
|
1320
|
+
@staticmethod
|
|
1321
|
+
@check_arguments
|
|
1322
|
+
def float_to_string(number: float, leading_zeroplaces: int, trailing_zeroplaces: int) -> str:
|
|
1323
|
+
plain_str = str(number)
|
|
1324
|
+
GeneralUtilities.assert_condition("." in plain_str)
|
|
1325
|
+
splitted: list[str] = plain_str.split(".")
|
|
1326
|
+
return splitted[0].zfill(leading_zeroplaces)+"."+splitted[1].ljust(trailing_zeroplaces, '0')
|
|
1327
|
+
|
|
1328
|
+
@staticmethod
|
|
1329
|
+
@check_arguments
|
|
1330
|
+
def process_is_running_by_name(process_name: str) -> bool:
|
|
1331
|
+
processes: list[psutil.Process] = list(psutil.process_iter())
|
|
1332
|
+
for p in processes:
|
|
1333
|
+
if p.name() == process_name:
|
|
1334
|
+
return True
|
|
1335
|
+
return False
|
|
1336
|
+
|
|
1337
|
+
@staticmethod
|
|
1338
|
+
@check_arguments
|
|
1339
|
+
def process_is_running_by_id(process_id: int) -> bool:
|
|
1340
|
+
processes: list[psutil.Process] = list(psutil.process_iter())
|
|
1341
|
+
for p in processes:
|
|
1342
|
+
if p.pid == process_id:
|
|
1343
|
+
return True
|
|
1344
|
+
return False
|
|
1345
|
+
|
|
1346
|
+
@staticmethod
|
|
1347
|
+
@check_arguments
|
|
1348
|
+
def kill_process(process_id: int, include_child_processes: bool) -> bool:
|
|
1349
|
+
if GeneralUtilities. process_is_running_by_id(process_id):
|
|
1350
|
+
GeneralUtilities.write_message_to_stdout(f"Process with id {process_id} is running. Terminating it...")
|
|
1351
|
+
process = psutil.Process(process_id)
|
|
1352
|
+
if include_child_processes:
|
|
1353
|
+
for child in process.children(recursive=True):
|
|
1354
|
+
if GeneralUtilities.process_is_running_by_id(child.pid):
|
|
1355
|
+
child.kill()
|
|
1356
|
+
if GeneralUtilities.process_is_running_by_id(process_id):
|
|
1357
|
+
process.kill()
|
|
1358
|
+
else:
|
|
1359
|
+
GeneralUtilities.write_message_to_stdout(f"Process with id {process_id} is not running anymore.")
|
|
1360
|
+
|
|
1361
|
+
@staticmethod
|
|
1362
|
+
@check_arguments
|
|
1363
|
+
def get_only_item_from_list(list_with_one_element:list):
|
|
1364
|
+
GeneralUtilities.assert_condition(len(list_with_one_element)==1,f"List does not contain exactly one item. It contains {len(list_with_one_element)} items.")
|
|
1365
|
+
return list_with_one_element[0]
|
|
1366
|
+
|
|
1367
|
+
@staticmethod
|
|
1368
|
+
@check_arguments
|
|
1369
|
+
def trim_newlines(s: str) -> str:
|
|
1370
|
+
return s.strip("\n")
|
|
1371
|
+
|
|
1372
|
+
@staticmethod
|
|
1373
|
+
@check_arguments
|
|
1374
|
+
def log_merger(folder:str):
|
|
1375
|
+
"""For each log file in this folder this function looks for log-rotation-files and merges them together again into one file."""
|
|
1376
|
+
all_files=GeneralUtilities.get_direct_files_of_folder(folder)
|
|
1377
|
+
for log_file in all_files:
|
|
1378
|
+
if "AutoRename" in log_file:
|
|
1379
|
+
continue
|
|
1380
|
+
filename=os.path.basename(log_file)
|
|
1381
|
+
filename_without_extension=Path(log_file).stem
|
|
1382
|
+
if not ".archive." in filename:
|
|
1383
|
+
|
|
1384
|
+
#merge with rotated logs
|
|
1385
|
+
if filename.endswith(".log") and not ".archive." in filename:
|
|
1386
|
+
rotated_log_files=sorted([f for f in all_files if os.path.basename(f).startswith(filename_without_extension+".archive.")], key=GeneralUtilities._internal_extract_log_file_number)
|
|
1387
|
+
result=GeneralUtilities.empty_string
|
|
1388
|
+
if 0<len(rotated_log_files):
|
|
1389
|
+
for rotated_log_file in rotated_log_files:
|
|
1390
|
+
result+="\n"+GeneralUtilities.read_text_from_file(rotated_log_file)
|
|
1391
|
+
result+="\n"+GeneralUtilities.read_text_from_file(log_file)
|
|
1392
|
+
GeneralUtilities.write_text_to_file(log_file, result)
|
|
1393
|
+
for rotated_log_file in rotated_log_files:
|
|
1394
|
+
GeneralUtilities.ensure_file_does_not_exist(rotated_log_file)
|
|
1395
|
+
|
|
1396
|
+
#normalize
|
|
1397
|
+
logs=GeneralUtilities.read_text_from_file(log_file)
|
|
1398
|
+
logs=logs.replace("\r\n", "\n")
|
|
1399
|
+
logs=logs.replace("\r", GeneralUtilities.empty_string)
|
|
1400
|
+
logs=re.sub(r"\n+", "\n", logs)
|
|
1401
|
+
logs=GeneralUtilities.trim_newlines(logs)
|
|
1402
|
+
GeneralUtilities.write_text_to_file(log_file, logs)
|
|
1403
|
+
|
|
1404
|
+
@staticmethod
|
|
1405
|
+
@check_arguments
|
|
1406
|
+
def _internal_extract_log_file_number(filename: str) -> int:
|
|
1407
|
+
m = re.search(r"\.archive\.(\d+)\.log$", filename)
|
|
1408
|
+
if m:
|
|
1409
|
+
return int(m.group(1))
|
|
1410
|
+
else:
|
|
1411
|
+
raise ValueError(f"Filename '{filename}' does not match the expected pattern for rotated log files.")
|
|
1412
|
+
|
|
1413
|
+
@staticmethod
|
|
1414
|
+
@check_arguments
|
|
1415
|
+
def _internal_load_resource(relative_path: str) -> bytes:
|
|
1416
|
+
res = resources.files("ScriptCollection.Resources")
|
|
1417
|
+
for part in relative_path.split("/"):
|
|
1418
|
+
res = res.joinpath(part)
|
|
1419
|
+
result= res.read_bytes()
|
|
1420
|
+
return result
|
|
1421
|
+
|
|
1422
|
+
@staticmethod
|
|
1423
|
+
@check_arguments
|
|
1424
|
+
def escape_json_string_value(str_value: str) -> str:
|
|
1425
|
+
"""For input 'a"b\\c' this function returns 'a\\"b\\\\c'. This function does not add surrounding quotes. It only escapes the characters inside the string."""
|
|
1426
|
+
json_escaped = json.dumps(str_value)
|
|
1427
|
+
return json_escaped[1:-1]
|
|
1428
|
+
|
|
1429
|
+
@staticmethod
|
|
1430
|
+
@check_arguments
|
|
1431
|
+
def escape_json_property_value(str_value: str) -> str:
|
|
1432
|
+
# remove all characters not in [a-zA-Z0-9_]
|
|
1433
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_]', '', str_value)
|
|
1434
|
+
|
|
1435
|
+
# check if result starts with [a-zA-Z_]
|
|
1436
|
+
if not sanitized or len(sanitized) == 0 or not re.match(r'[a-zA-Z_]', sanitized[0]):
|
|
1437
|
+
raise ValueError(f"Invalid JSON property name after sanitizing: '{sanitized}'. Value must start with [a-zA-Z_].")
|
|
1438
|
+
|
|
1439
|
+
return sanitized
|
|
1440
|
+
|
|
1441
|
+
@staticmethod
|
|
1442
|
+
@check_arguments
|
|
1443
|
+
def escape_yaml_string_value(str_value: str) -> str:
|
|
1444
|
+
"""For input 'a"b\\c' this function returns 'a\\"b\\\\c'. This function does not add surrounding quotes. It only escapes the characters inside the string."""
|
|
1445
|
+
result= GeneralUtilities.escape_json_string_value(str_value)
|
|
1446
|
+
#do other adaptions here if desired/required
|
|
1447
|
+
return result
|
|
1448
|
+
|
|
1449
|
+
@staticmethod
|
|
1450
|
+
@check_arguments
|
|
1451
|
+
def escape_yaml_property_value(str_value: str) -> str:
|
|
1452
|
+
result= GeneralUtilities.escape_json_property_value(str_value)
|
|
1453
|
+
#do other adaptions here if desired/required
|
|
1454
|
+
return result
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
@staticmethod
|
|
1458
|
+
@check_arguments
|
|
1459
|
+
def get_current_platform() -> Platform:
|
|
1460
|
+
system = platform.system().lower()
|
|
1461
|
+
machine = platform.machine().lower()
|
|
1462
|
+
|
|
1463
|
+
if system == "windows" and machine in ("x86_64", "amd64"):
|
|
1464
|
+
return Platform.Windows_AMD64
|
|
1465
|
+
elif system == "linux" and machine in ("x86_64", "amd64"):
|
|
1466
|
+
return Platform.Linux_AMD64
|
|
1467
|
+
elif system == "linux" and machine in ("arm64", "aarch64"):
|
|
1468
|
+
return Platform.Linux_ARM64
|
|
1469
|
+
elif system == "darwin" and machine in ("x86_64", "amd64"):
|
|
1470
|
+
return Platform.MacOS_ARM64
|
|
1471
|
+
else:
|
|
1472
|
+
raise ValueError(f"Unsupported platform: {system}/{machine}")
|
|
1473
|
+
|
|
1474
|
+
@staticmethod
|
|
1475
|
+
@check_arguments
|
|
1476
|
+
def current_system_is_x64():
|
|
1477
|
+
arch = platform.machine().lower()
|
|
1478
|
+
return arch in ("x86_64", "amd64")
|
|
1479
|
+
|
|
1480
|
+
@staticmethod
|
|
1481
|
+
@check_arguments
|
|
1482
|
+
def current_system_is_arm64():
|
|
1483
|
+
arch = platform.machine().lower()
|
|
1484
|
+
return arch in ("arm", "aarch64")
|
|
1485
|
+
|
|
1486
|
+
@staticmethod
|
|
1487
|
+
@check_arguments
|
|
1488
|
+
def platform_to_short_str(platform_value: Platform) -> str:
|
|
1489
|
+
mapping = {
|
|
1490
|
+
Platform.Windows_AMD64: "win-x64",
|
|
1491
|
+
Platform.Linux_AMD64: "linux-x64",
|
|
1492
|
+
Platform.Linux_ARM64: "linux-arm64",
|
|
1493
|
+
Platform.MacOS_ARM64: "osx-arm64",
|
|
1494
|
+
}
|
|
1495
|
+
return mapping[platform_value]
|
|
1496
|
+
|
|
1497
|
+
@staticmethod
|
|
1498
|
+
@check_arguments
|
|
1499
|
+
def platform_from_short_str(platform_str: str) -> Platform:
|
|
1500
|
+
mapping = {
|
|
1501
|
+
"win-x64": Platform.Windows_AMD64,
|
|
1502
|
+
"linux-x64": Platform.Linux_AMD64,
|
|
1503
|
+
"linux-arm64": Platform.Linux_ARM64,
|
|
1504
|
+
"osx-arm64": Platform.MacOS_ARM64,
|
|
1505
|
+
}
|
|
1506
|
+
if platform_str not in mapping:
|
|
1507
|
+
raise ValueError(f"Unsupported platform string: {platform_str}")
|
|
1508
|
+
return mapping[platform_str]
|
|
1509
|
+
|
|
1510
|
+
@staticmethod
|
|
1511
|
+
@check_arguments
|
|
1512
|
+
def platform_to_dash_str(platform_value: Platform) -> str:
|
|
1513
|
+
mapping = {
|
|
1514
|
+
Platform.Windows_AMD64: "Windows-x64",
|
|
1515
|
+
Platform.Linux_AMD64: "Linux-x64",
|
|
1516
|
+
Platform.Linux_ARM64: "Linux-arm64",
|
|
1517
|
+
Platform.MacOS_ARM64: "MacOS-arm64",
|
|
1518
|
+
}
|
|
1519
|
+
return mapping[platform_value]
|
|
1520
|
+
|
|
1521
|
+
@staticmethod
|
|
1522
|
+
@check_arguments
|
|
1523
|
+
def platform_from_dash_str(platform_str: str) -> Platform:
|
|
1524
|
+
mapping = {
|
|
1525
|
+
"Windows-x64": Platform.Windows_AMD64,
|
|
1526
|
+
"Linux-x64": Platform.Linux_AMD64,
|
|
1527
|
+
"Linux-arm64": Platform.Linux_ARM64,
|
|
1528
|
+
"MacOS-arm64": Platform.MacOS_ARM64,
|
|
1529
|
+
}
|
|
1530
|
+
if platform_str not in mapping:
|
|
1531
|
+
raise ValueError(f"Unsupported platform string: {platform_str}")
|
|
1532
|
+
return mapping[platform_str]
|
|
1533
|
+
|
|
1534
|
+
@staticmethod
|
|
1535
|
+
@check_arguments
|
|
1536
|
+
def platform_to_docker_platform_str(platform_value: Platform) -> str:
|
|
1537
|
+
mapping = {
|
|
1538
|
+
Platform.Windows_AMD64: "windows/amd64",
|
|
1539
|
+
Platform.Linux_AMD64: "linux/amd64",
|
|
1540
|
+
Platform.Linux_ARM64: "linux/arm64",
|
|
1541
|
+
Platform.MacOS_ARM64: "linux/arm64", # macOS → linux container
|
|
1542
|
+
}
|
|
1543
|
+
return mapping[platform_value]
|
|
1544
|
+
|
|
1545
|
+
@staticmethod
|
|
1546
|
+
@check_arguments
|
|
1547
|
+
def platform_to_dotnet_runtime_identifier(platform_value: Platform) -> str:
|
|
1548
|
+
mapping = {
|
|
1549
|
+
Platform.Windows_AMD64: "win-x64",
|
|
1550
|
+
Platform.Linux_AMD64: "linux-x64",
|
|
1551
|
+
Platform.Linux_ARM64: "linux-arm64",
|
|
1552
|
+
Platform.MacOS_ARM64: "osx-arm64",
|
|
1553
|
+
}
|
|
1554
|
+
return mapping[platform_value]
|
|
1555
|
+
|
|
1556
|
+
@staticmethod
|
|
1557
|
+
@check_arguments
|
|
1558
|
+
def platform_to_go_runtime_identifier(platform_value: Platform) -> str:
|
|
1559
|
+
mapping = {
|
|
1560
|
+
Platform.Linux_AMD64: "amd64",
|
|
1561
|
+
Platform.Linux_ARM64: "arm64",
|
|
1562
|
+
}
|
|
1563
|
+
return mapping[platform_value]
|
|
1564
|
+
|
|
1565
|
+
@staticmethod
|
|
1566
|
+
@check_arguments
|
|
1567
|
+
def get_all_platforms() -> list[Platform]:
|
|
1568
|
+
return [
|
|
1569
|
+
Platform.Windows_AMD64,
|
|
1570
|
+
Platform.Linux_AMD64,
|
|
1571
|
+
Platform.Linux_ARM64,
|
|
1572
|
+
Platform.MacOS_ARM64,
|
|
1573
|
+
]
|
|
1574
|
+
|
|
1575
|
+
@staticmethod
|
|
1576
|
+
@check_arguments
|
|
1577
|
+
def get_python_executable() -> str:
|
|
1578
|
+
#virtual_env = os.environ.get("VIRTUAL_ENV")
|
|
1579
|
+
#if GeneralUtilities.string_has_content(virtual_env):
|
|
1580
|
+
# if platform.system() == "Windows":
|
|
1581
|
+
# result = os.path.join(virtual_env, "Scripts", "python.exe")
|
|
1582
|
+
# else:
|
|
1583
|
+
# result = os.path.join(virtual_env, "bin", "python")
|
|
1584
|
+
if GeneralUtilities.string_has_content(sys.executable):
|
|
1585
|
+
result = sys.executable
|
|
1586
|
+
else:
|
|
1587
|
+
result = "python"
|
|
1588
|
+
GeneralUtilities.assert_condition(os.path.isfile(result), f"Python executable does not exist: '{result}'")
|
|
1589
|
+
return result
|