ScriptCollection 3.5.16__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.
- ScriptCollection/AnionBuildPlatform.py +206 -0
- ScriptCollection/{UpdateCertificates.py → CertificateUpdater.py} +69 -46
- ScriptCollection/Executables.py +515 -18
- ScriptCollection/GeneralUtilities.py +1272 -873
- ScriptCollection/ImageUpdater.py +648 -0
- ScriptCollection/ProgramRunnerBase.py +10 -10
- ScriptCollection/ProgramRunnerMock.py +2 -0
- ScriptCollection/ProgramRunnerPopen.py +7 -1
- ScriptCollection/ProgramRunnerSudo.py +108 -0
- ScriptCollection/SCLog.py +115 -0
- ScriptCollection/ScriptCollectionCore.py +942 -266
- ScriptCollection/TFCPS/Docker/TFCPS_CodeUnitSpecific_Docker.py +95 -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 +485 -0
- ScriptCollection/TFCPS/DotNet/__init__.py +0 -0
- ScriptCollection/TFCPS/Flutter/TFCPS_CodeUnitSpecific_Flutter.py +130 -0
- ScriptCollection/TFCPS/Flutter/__init__.py +0 -0
- ScriptCollection/TFCPS/Go/TFCPS_CodeUnitSpecific_Go.py +74 -0
- ScriptCollection/TFCPS/Go/__init__.py +0 -0
- ScriptCollection/TFCPS/NodeJS/TFCPS_CodeUnitSpecific_NodeJS.py +131 -0
- ScriptCollection/TFCPS/NodeJS/__init__.py +0 -0
- ScriptCollection/TFCPS/Python/TFCPS_CodeUnitSpecific_Python.py +227 -0
- ScriptCollection/TFCPS/Python/__init__.py +0 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnitSpecific_Base.py +418 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnit_BuildCodeUnit.py +128 -0
- ScriptCollection/TFCPS/TFCPS_CodeUnit_BuildCodeUnits.py +136 -0
- ScriptCollection/TFCPS/TFCPS_CreateRelease.py +95 -0
- ScriptCollection/TFCPS/TFCPS_Generic.py +43 -0
- ScriptCollection/TFCPS/TFCPS_MergeToMain.py +122 -0
- ScriptCollection/TFCPS/TFCPS_MergeToStable.py +350 -0
- ScriptCollection/TFCPS/TFCPS_PreBuildCodeunitsScript.py +47 -0
- ScriptCollection/TFCPS/TFCPS_Tools_General.py +1356 -0
- ScriptCollection/TFCPS/__init__.py +0 -0
- {ScriptCollection-3.5.16.dist-info → scriptcollection-4.0.78.dist-info}/METADATA +23 -22
- scriptcollection-4.0.78.dist-info/RECORD +43 -0
- {ScriptCollection-3.5.16.dist-info → scriptcollection-4.0.78.dist-info}/WHEEL +1 -1
- {ScriptCollection-3.5.16.dist-info → scriptcollection-4.0.78.dist-info}/entry_points.txt +32 -0
- ScriptCollection/ProgramRunnerEpew.py +0 -122
- ScriptCollection/RPStream.py +0 -42
- ScriptCollection/TasksForCommonProjectStructure.py +0 -2625
- ScriptCollection-3.5.16.dist-info/RECORD +0 -16
- {ScriptCollection-3.5.16.dist-info → scriptcollection-4.0.78.dist-info}/top_level.txt +0 -0
|
@@ -1,873 +1,1272 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@staticmethod
|
|
135
|
-
@check_arguments
|
|
136
|
-
def
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
@
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
GeneralUtilities.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
@
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
@staticmethod
|
|
291
|
-
@check_arguments
|
|
292
|
-
def
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
@
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
@
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
@
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
@staticmethod
|
|
339
|
-
@check_arguments
|
|
340
|
-
def
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
@
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
@
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
@
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
@
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
@staticmethod
|
|
380
|
-
@check_arguments
|
|
381
|
-
def
|
|
382
|
-
if
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
@
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
@staticmethod
|
|
393
|
-
@check_arguments
|
|
394
|
-
def
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
@
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
@
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
@
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
@
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
@
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
@staticmethod
|
|
519
|
-
@check_arguments
|
|
520
|
-
def
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
@
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
@
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
@staticmethod
|
|
598
|
-
@check_arguments
|
|
599
|
-
def
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
@
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
@
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
@
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
@
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return
|
|
738
|
-
|
|
739
|
-
@staticmethod
|
|
740
|
-
@check_arguments
|
|
741
|
-
def
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
return
|
|
822
|
-
|
|
823
|
-
@staticmethod
|
|
824
|
-
@check_arguments
|
|
825
|
-
def
|
|
826
|
-
if
|
|
827
|
-
|
|
828
|
-
return
|
|
829
|
-
|
|
830
|
-
@staticmethod
|
|
831
|
-
@check_arguments
|
|
832
|
-
def
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return
|
|
839
|
-
|
|
840
|
-
@staticmethod
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
from os import listdir
|
|
4
|
+
from os.path import isfile, join, isdir
|
|
5
|
+
import codecs
|
|
6
|
+
import platform
|
|
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 enum import Enum
|
|
20
|
+
import traceback
|
|
21
|
+
import warnings
|
|
22
|
+
import functools
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from datetime import datetime, timedelta, date, timezone
|
|
25
|
+
import typing
|
|
26
|
+
from packaging.version import Version
|
|
27
|
+
import psutil
|
|
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
|
|
41
|
+
|
|
42
|
+
def __init__(self,dependencyname:str,current_version:str):
|
|
43
|
+
self.dependencyname=dependencyname
|
|
44
|
+
self.current_version=current_version
|
|
45
|
+
|
|
46
|
+
class GeneralUtilities:
|
|
47
|
+
|
|
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"
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def is_generic(t: typing.Type):
|
|
60
|
+
return hasattr(t, "__origin__")
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def is_debugger_attached():
|
|
64
|
+
return sys.gettrace() is not None
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def check_arguments(function):
|
|
68
|
+
def __check_function(*args, **named_args):
|
|
69
|
+
parameters: list = inspect.getfullargspec(function)[0].copy()
|
|
70
|
+
arguments: list = list(tuple(args)).copy()
|
|
71
|
+
if "self" in parameters:
|
|
72
|
+
parameters.remove("self")
|
|
73
|
+
arguments.pop(0)
|
|
74
|
+
for index, argument in enumerate(arguments):
|
|
75
|
+
if argument is not None: # Check type of None is not possible. None is always a valid argument-value
|
|
76
|
+
if parameters[index] in function.__annotations__: # Check if a type-hint for parameter exist. If not, no parameter-type available for argument-type-check
|
|
77
|
+
# Check type of arguments if the type is a generic type seems to be impossible.
|
|
78
|
+
if not GeneralUtilities.is_generic(function.__annotations__[parameters[index]]):
|
|
79
|
+
if not isinstance(argument, function.__annotations__[parameters[index]]):
|
|
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)))
|
|
81
|
+
for index, named_argument in enumerate(named_args):
|
|
82
|
+
if named_args[named_argument] is not None:
|
|
83
|
+
if parameters[index] in function.__annotations__:
|
|
84
|
+
if not GeneralUtilities.is_generic(function.__annotations__.get(named_argument)):
|
|
85
|
+
if not isinstance(named_args[named_argument], 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)}")
|
|
87
|
+
return function(*args, **named_args)
|
|
88
|
+
__check_function.__doc__ = function.__doc__
|
|
89
|
+
return __check_function
|
|
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
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
@check_arguments
|
|
103
|
+
def args_array_surround_with_quotes_if_required(arguments: list[str]) -> list[str]:
|
|
104
|
+
result = []
|
|
105
|
+
for argument in arguments:
|
|
106
|
+
if " " in argument and not (argument.startswith('"') and argument.endswith('"')):
|
|
107
|
+
result.append(f'"{argument}"')
|
|
108
|
+
else:
|
|
109
|
+
result.append(argument)
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
@check_arguments
|
|
114
|
+
def string_to_lines(string: str, add_empty_lines: bool = True, adapt_lines: bool = True) -> list[str]:
|
|
115
|
+
result : list[str] = list[str]()
|
|
116
|
+
lines : list[str] = list[str]()
|
|
117
|
+
if (string is not None):
|
|
118
|
+
if ("\n" in string):
|
|
119
|
+
lines = string.split("\n")
|
|
120
|
+
else:
|
|
121
|
+
lines.append(string)
|
|
122
|
+
for rawline in lines:
|
|
123
|
+
if adapt_lines:
|
|
124
|
+
line = rawline.replace("\r", "").strip()
|
|
125
|
+
else:
|
|
126
|
+
line = rawline
|
|
127
|
+
if GeneralUtilities.string_is_none_or_whitespace(line):
|
|
128
|
+
if add_empty_lines:
|
|
129
|
+
result.append(line)
|
|
130
|
+
else:
|
|
131
|
+
result.append(line)
|
|
132
|
+
return result
|
|
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
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
@check_arguments
|
|
143
|
+
def datetime_to_string(value: datetime) -> str:
|
|
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
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
@check_arguments
|
|
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
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
@check_arguments
|
|
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)
|
|
255
|
+
for file in GeneralUtilities.get_direct_files_of_folder(srcDirFull):
|
|
256
|
+
filename = os.path.basename(file)
|
|
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)
|
|
266
|
+
else:
|
|
267
|
+
shutil.copy(file, dstDirFull)
|
|
268
|
+
for sub_folder in GeneralUtilities.get_direct_folders_of_folder(srcDirFull):
|
|
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)
|
|
275
|
+
else:
|
|
276
|
+
raise ValueError(f"Folder '{source_directory}' does not exist")
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
@check_arguments
|
|
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:
|
|
281
|
+
if verbose:
|
|
282
|
+
GeneralUtilities.write_message_to_stdout(f"Replace '{replace_from_regex}' to '{replace_to_regex}' in '{file}'")
|
|
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)
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
@check_arguments
|
|
292
|
+
def replace_regex_in_file(file: str, replace_from_regex: str, replace_to_regex: str, encoding="utf-8") -> None:
|
|
293
|
+
with open(file, encoding=encoding, mode="r") as f:
|
|
294
|
+
content = f.read()
|
|
295
|
+
content = re.sub(replace_from_regex, replace_to_regex, content)
|
|
296
|
+
with open(file, encoding=encoding, mode="w") as f:
|
|
297
|
+
f.write(content)
|
|
298
|
+
|
|
299
|
+
@staticmethod
|
|
300
|
+
@check_arguments
|
|
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}\"")
|
|
303
|
+
GeneralUtilities.replace_regex_in_file(file, f"<{tag}>.*</{tag}>", f"<{tag}>{new_value}</{tag}>", encoding)
|
|
304
|
+
|
|
305
|
+
@staticmethod
|
|
306
|
+
@check_arguments
|
|
307
|
+
def update_version_in_csproj_file(file: str, target_version: str) -> None:
|
|
308
|
+
GeneralUtilities.replace_xmltag_in_file(file, "Version", target_version)
|
|
309
|
+
GeneralUtilities.replace_xmltag_in_file(file, "AssemblyVersion", target_version + ".0")
|
|
310
|
+
GeneralUtilities.replace_xmltag_in_file(file, "FileVersion", target_version + ".0")
|
|
311
|
+
|
|
312
|
+
@staticmethod
|
|
313
|
+
@check_arguments
|
|
314
|
+
def replace_underscores_in_text(text: str, replacements: dict) -> str:
|
|
315
|
+
changed = True
|
|
316
|
+
while changed:
|
|
317
|
+
changed = False
|
|
318
|
+
for key, value in replacements.items():
|
|
319
|
+
previousValue = text
|
|
320
|
+
text = text.replace(f"__{key}__", value)
|
|
321
|
+
if (not text == previousValue):
|
|
322
|
+
changed = True
|
|
323
|
+
return text
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
@check_arguments
|
|
327
|
+
def replace_underscores_in_file(file: str, replacements: dict, encoding: str = "utf-8"):
|
|
328
|
+
text = GeneralUtilities.read_text_from_file(file, encoding)
|
|
329
|
+
text = GeneralUtilities.replace_underscores_in_text(text, replacements)
|
|
330
|
+
GeneralUtilities.write_text_to_file(file, text, encoding)
|
|
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
|
+
|
|
387
|
+
@staticmethod
|
|
388
|
+
@check_arguments
|
|
389
|
+
def write_message_to_stdout(message: str):
|
|
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()
|
|
399
|
+
|
|
400
|
+
@staticmethod
|
|
401
|
+
@check_arguments
|
|
402
|
+
def write_message_to_stderr(message: str):
|
|
403
|
+
GeneralUtilities.write_message_to_stderr_advanced(message, True, True, True)
|
|
404
|
+
|
|
405
|
+
@staticmethod
|
|
406
|
+
@check_arguments
|
|
407
|
+
def get_advanced_errormessage_for_os_error(os_error: OSError) -> str:
|
|
408
|
+
if GeneralUtilities.string_has_content(os_error.filename2):
|
|
409
|
+
secondpath = f" {os_error.filename2}"
|
|
410
|
+
else:
|
|
411
|
+
secondpath = GeneralUtilities.empty_string
|
|
412
|
+
return f"Related path(s): {os_error.filename}{secondpath}"
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
@check_arguments
|
|
416
|
+
def write_exception_to_stderr(exception: Exception, extra_message: str = None):
|
|
417
|
+
GeneralUtilities.write_exception_to_stderr_with_traceback(exception, None, extra_message)
|
|
418
|
+
|
|
419
|
+
@staticmethod
|
|
420
|
+
@check_arguments
|
|
421
|
+
def write_exception_to_stderr_with_traceback(exception: Exception, current_traceback=None, extra_message: str = None):
|
|
422
|
+
GeneralUtilities.write_message_to_stderr("Exception(")
|
|
423
|
+
GeneralUtilities.write_message_to_stderr("Type: " + str(type(exception)))
|
|
424
|
+
GeneralUtilities.write_message_to_stderr("Message: " + str(exception))
|
|
425
|
+
if extra_message is not None:
|
|
426
|
+
GeneralUtilities.write_message_to_stderr("Extra-message: " + str(extra_message))
|
|
427
|
+
if isinstance(exception, OSError):
|
|
428
|
+
GeneralUtilities.write_message_to_stderr(GeneralUtilities.get_advanced_errormessage_for_os_error(exception))
|
|
429
|
+
if current_traceback is not None:
|
|
430
|
+
GeneralUtilities.write_message_to_stderr("Traceback: " + current_traceback.format_exc())
|
|
431
|
+
GeneralUtilities.write_message_to_stderr(")")
|
|
432
|
+
|
|
433
|
+
@staticmethod
|
|
434
|
+
@check_arguments
|
|
435
|
+
def string_has_content(string: str) -> bool:
|
|
436
|
+
if string is None:
|
|
437
|
+
return False
|
|
438
|
+
else:
|
|
439
|
+
return len(string.strip()) > 0
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
@check_arguments
|
|
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)
|
|
449
|
+
|
|
450
|
+
@staticmethod
|
|
451
|
+
@check_arguments
|
|
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
|
|
463
|
+
|
|
464
|
+
@staticmethod
|
|
465
|
+
@check_arguments
|
|
466
|
+
def string_has_nonwhitespace_content(string: str) -> bool:
|
|
467
|
+
if string is None:
|
|
468
|
+
return False
|
|
469
|
+
else:
|
|
470
|
+
return len(string.strip()) > 0
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
@check_arguments
|
|
474
|
+
def string_is_none_or_empty(argument: str) -> bool:
|
|
475
|
+
if argument is None:
|
|
476
|
+
return True
|
|
477
|
+
type_of_argument = type(argument)
|
|
478
|
+
if type_of_argument == str:
|
|
479
|
+
return argument == GeneralUtilities.empty_string
|
|
480
|
+
else:
|
|
481
|
+
raise ValueError(f"expected string-variable in argument of string_is_none_or_empty but the type was '{str(type_of_argument)}'")
|
|
482
|
+
|
|
483
|
+
@staticmethod
|
|
484
|
+
@check_arguments
|
|
485
|
+
def string_is_none_or_whitespace(string: str) -> bool:
|
|
486
|
+
if GeneralUtilities.string_is_none_or_empty(string):
|
|
487
|
+
return True
|
|
488
|
+
else:
|
|
489
|
+
return string.strip() == GeneralUtilities.empty_string
|
|
490
|
+
|
|
491
|
+
@staticmethod
|
|
492
|
+
@check_arguments
|
|
493
|
+
def strip_new_line_character(value: str) -> str:
|
|
494
|
+
while not GeneralUtilities.__strip_new_line_character_helper_value_is_ok(value):
|
|
495
|
+
value = GeneralUtilities.__strip_new_line_character_helper_normalize_value(value)
|
|
496
|
+
return value
|
|
497
|
+
|
|
498
|
+
@staticmethod
|
|
499
|
+
@check_arguments
|
|
500
|
+
def __strip_new_line_character_helper_value_is_ok(value: str) -> bool:
|
|
501
|
+
if value.startswith("\r") or value.endswith("\r"):
|
|
502
|
+
return False
|
|
503
|
+
if value.startswith("\n") or value.endswith("\n"):
|
|
504
|
+
return False
|
|
505
|
+
return True
|
|
506
|
+
|
|
507
|
+
@staticmethod
|
|
508
|
+
@check_arguments
|
|
509
|
+
def __strip_new_line_character_helper_normalize_value(value: str) -> str:
|
|
510
|
+
return value.strip('\n').strip('\r')
|
|
511
|
+
|
|
512
|
+
@staticmethod
|
|
513
|
+
@check_arguments
|
|
514
|
+
def file_ends_with_newline(file: str) -> bool:
|
|
515
|
+
with open(file, "rb") as file_object:
|
|
516
|
+
return GeneralUtilities.ends_with_newline_character(file_object.read())
|
|
517
|
+
|
|
518
|
+
@staticmethod
|
|
519
|
+
@check_arguments
|
|
520
|
+
def ends_with_newline_character(content: bytes) -> bool:
|
|
521
|
+
result = content.endswith(GeneralUtilities.string_to_bytes("\n"))
|
|
522
|
+
return result
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
@check_arguments
|
|
526
|
+
def file_ends_with_content(file: str) -> bool:
|
|
527
|
+
content = GeneralUtilities.read_binary_from_file(file)
|
|
528
|
+
if len(content) == 0:
|
|
529
|
+
return False
|
|
530
|
+
else:
|
|
531
|
+
if GeneralUtilities.ends_with_newline_character(content):
|
|
532
|
+
return False
|
|
533
|
+
else:
|
|
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
|
|
543
|
+
|
|
544
|
+
@staticmethod
|
|
545
|
+
@check_arguments
|
|
546
|
+
def append_line_to_file(file: str, line: str, encoding: str = "utf-8") -> None:
|
|
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
|
|
570
|
+
|
|
571
|
+
@staticmethod
|
|
572
|
+
@check_arguments
|
|
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.")
|
|
575
|
+
with open(file, "a", encoding=encoding) as fileObject:
|
|
576
|
+
fileObject.write(content)
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
@check_arguments
|
|
580
|
+
def ensure_directory_exists(path: str) -> None:
|
|
581
|
+
if not os.path.isdir(path):
|
|
582
|
+
os.makedirs(path)
|
|
583
|
+
|
|
584
|
+
@staticmethod
|
|
585
|
+
@check_arguments
|
|
586
|
+
def ensure_file_exists(path: str) -> None:
|
|
587
|
+
if (not os.path.isfile(path)):
|
|
588
|
+
with open(path, "a+", encoding="utf-8"):
|
|
589
|
+
pass
|
|
590
|
+
|
|
591
|
+
@staticmethod
|
|
592
|
+
@check_arguments
|
|
593
|
+
def __remove_readonly(func, path, _):
|
|
594
|
+
os.chmod(path, stat.S_IWRITE)
|
|
595
|
+
func(path)
|
|
596
|
+
|
|
597
|
+
@staticmethod
|
|
598
|
+
@check_arguments
|
|
599
|
+
def __rmtree(directory: str) -> None:
|
|
600
|
+
shutil.rmtree(directory, onerror=GeneralUtilities.__remove_readonly) # pylint: disable=deprecated-argument
|
|
601
|
+
|
|
602
|
+
@staticmethod
|
|
603
|
+
@check_arguments
|
|
604
|
+
def ensure_directory_does_not_exist(path: str) -> None:
|
|
605
|
+
if (os.path.isdir(path)):
|
|
606
|
+
for root, dirs, files in os.walk(path, topdown=False):
|
|
607
|
+
for name in files:
|
|
608
|
+
filename = os.path.join(root, name)
|
|
609
|
+
os.chmod(filename, stat.S_IWUSR)
|
|
610
|
+
os.remove(filename)
|
|
611
|
+
for name in dirs:
|
|
612
|
+
GeneralUtilities.__rmtree(os.path.join(root, name))
|
|
613
|
+
GeneralUtilities.__rmtree(path)
|
|
614
|
+
|
|
615
|
+
@staticmethod
|
|
616
|
+
@check_arguments
|
|
617
|
+
def ensure_folder_exists_and_is_empty(path: str) -> None:
|
|
618
|
+
GeneralUtilities.ensure_directory_exists(path)
|
|
619
|
+
for filename in os.listdir(path):
|
|
620
|
+
file_path = os.path.join(path, filename)
|
|
621
|
+
if os.path.isfile(file_path):
|
|
622
|
+
os.remove(file_path)
|
|
623
|
+
if os.path.islink(file_path):
|
|
624
|
+
os.unlink(file_path)
|
|
625
|
+
elif os.path.isdir(file_path):
|
|
626
|
+
shutil.rmtree(file_path)
|
|
627
|
+
|
|
628
|
+
@staticmethod
|
|
629
|
+
@check_arguments
|
|
630
|
+
def ensure_file_does_not_exist(path: str) -> None:
|
|
631
|
+
if (os.path.isfile(path)):
|
|
632
|
+
os.remove(path)
|
|
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
|
+
|
|
642
|
+
@staticmethod
|
|
643
|
+
@check_arguments
|
|
644
|
+
def format_xml_file(filepath: str) -> None:
|
|
645
|
+
GeneralUtilities.format_xml_file_with_encoding(filepath, "utf-8")
|
|
646
|
+
|
|
647
|
+
@staticmethod
|
|
648
|
+
@check_arguments
|
|
649
|
+
def format_xml_file_with_encoding(filepath: str, encoding: str) -> None:
|
|
650
|
+
with codecs.open(filepath, 'r', encoding=encoding) as file:
|
|
651
|
+
text = file.read()
|
|
652
|
+
text = parse(text).toprettyxml()
|
|
653
|
+
with codecs.open(filepath, 'w', encoding=encoding) as file:
|
|
654
|
+
file.write(text)
|
|
655
|
+
|
|
656
|
+
@staticmethod
|
|
657
|
+
@check_arguments
|
|
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.")
|
|
660
|
+
sectorsPerCluster = ctypes.c_ulonglong(0)
|
|
661
|
+
bytesPerSector = ctypes.c_ulonglong(0)
|
|
662
|
+
rootPathName = ctypes.c_wchar_p(diskpath)
|
|
663
|
+
ctypes.windll.kernel32.GetDiskFreeSpaceW(rootPathName, ctypes.pointer(sectorsPerCluster), ctypes.pointer(bytesPerSector), None, None)
|
|
664
|
+
return (sectorsPerCluster.value, bytesPerSector.value)
|
|
665
|
+
|
|
666
|
+
@staticmethod
|
|
667
|
+
@check_arguments
|
|
668
|
+
def ensure_path_is_not_quoted(path: str) -> str:
|
|
669
|
+
if (path.startswith("\"") and path.endswith("\"")) or (path.startswith("'") and path.endswith("'")):
|
|
670
|
+
path = path[1:]
|
|
671
|
+
path = path[:-1]
|
|
672
|
+
return path
|
|
673
|
+
else:
|
|
674
|
+
return path
|
|
675
|
+
|
|
676
|
+
@staticmethod
|
|
677
|
+
@check_arguments
|
|
678
|
+
def get_missing_files(folderA: str, folderB: str) -> list:
|
|
679
|
+
folderA_length = len(folderA)
|
|
680
|
+
result = []
|
|
681
|
+
for fileA in GeneralUtilities.absolute_file_paths(folderA):
|
|
682
|
+
file = fileA[folderA_length:]
|
|
683
|
+
fileB = folderB + file
|
|
684
|
+
if not os.path.isfile(fileB):
|
|
685
|
+
result.append(fileB)
|
|
686
|
+
return result
|
|
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
|
+
|
|
698
|
+
@staticmethod
|
|
699
|
+
@check_arguments
|
|
700
|
+
def write_lines_to_file(file: str, lines: list, encoding="utf-8") -> None:
|
|
701
|
+
lines = [GeneralUtilities.strip_new_line_character(line) for line in lines]
|
|
702
|
+
content = "\n".join(lines)
|
|
703
|
+
GeneralUtilities.write_text_to_file(file, content, encoding)
|
|
704
|
+
|
|
705
|
+
@staticmethod
|
|
706
|
+
@check_arguments
|
|
707
|
+
def write_text_to_file(file: str, content: str, encoding="utf-8") -> None:
|
|
708
|
+
GeneralUtilities.write_binary_to_file(file, bytes(bytearray(content, encoding)))
|
|
709
|
+
|
|
710
|
+
@staticmethod
|
|
711
|
+
@check_arguments
|
|
712
|
+
def write_binary_to_file(file: str, content: bytes) -> None:
|
|
713
|
+
with open(file, "wb") as file_object:
|
|
714
|
+
file_object.write(content)
|
|
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
|
+
|
|
725
|
+
@staticmethod
|
|
726
|
+
@check_arguments
|
|
727
|
+
def read_lines_from_file(file: str, encoding="utf-8") -> list[str]:
|
|
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)]
|
|
738
|
+
|
|
739
|
+
@staticmethod
|
|
740
|
+
@check_arguments
|
|
741
|
+
def read_text_from_file(file: str, encoding="utf-8") -> str:
|
|
742
|
+
GeneralUtilities.assert_file_exists(file)
|
|
743
|
+
return GeneralUtilities.bytes_to_string(GeneralUtilities.read_binary_from_file(file), encoding)
|
|
744
|
+
|
|
745
|
+
@staticmethod
|
|
746
|
+
@check_arguments
|
|
747
|
+
def read_binary_from_file(file: str) -> bytes:
|
|
748
|
+
with open(file, "rb") as file_object:
|
|
749
|
+
return file_object.read()
|
|
750
|
+
|
|
751
|
+
@staticmethod
|
|
752
|
+
@check_arguments
|
|
753
|
+
def timedelta_to_simple_string(delta: timedelta) -> str:
|
|
754
|
+
return (datetime(1970, 1, 1, 0, 0, 0) + delta).strftime('%H:%M:%S')
|
|
755
|
+
|
|
756
|
+
@staticmethod
|
|
757
|
+
@check_arguments
|
|
758
|
+
def resolve_relative_path_from_current_working_directory(path: str) -> str:
|
|
759
|
+
return GeneralUtilities.resolve_relative_path(path, os.getcwd())
|
|
760
|
+
|
|
761
|
+
@staticmethod
|
|
762
|
+
@check_arguments
|
|
763
|
+
def resolve_relative_path(path: str, base_path: str):
|
|
764
|
+
if (os.path.isabs(path)):
|
|
765
|
+
return path
|
|
766
|
+
else:
|
|
767
|
+
return str(Path(os.path.join(base_path, path)).resolve())
|
|
768
|
+
|
|
769
|
+
@staticmethod
|
|
770
|
+
@check_arguments
|
|
771
|
+
def get_metadata_for_file_for_clone_folder_structure(file: str) -> str:
|
|
772
|
+
size = os.path.getsize(file)
|
|
773
|
+
last_modified_timestamp = os.path.getmtime(file)
|
|
774
|
+
hash_value = GeneralUtilities.get_sha256_of_file(file)
|
|
775
|
+
last_access_timestamp = os.path.getatime(file)
|
|
776
|
+
return f'{{"size":"{size}","sha256":"{hash_value}","mtime":"{last_modified_timestamp}","atime":"{last_access_timestamp}"}}'
|
|
777
|
+
|
|
778
|
+
@staticmethod
|
|
779
|
+
@check_arguments
|
|
780
|
+
def clone_folder_structure(source: str, target: str, copy_only_metadata: bool):
|
|
781
|
+
source = GeneralUtilities.resolve_relative_path(source, os.getcwd())
|
|
782
|
+
target = GeneralUtilities.resolve_relative_path(target, os.getcwd())
|
|
783
|
+
length_of_source = len(source)
|
|
784
|
+
for source_file in GeneralUtilities.absolute_file_paths(source):
|
|
785
|
+
target_file = target+source_file[length_of_source:]
|
|
786
|
+
GeneralUtilities.ensure_directory_exists(os.path.dirname(target_file))
|
|
787
|
+
if copy_only_metadata:
|
|
788
|
+
with open(target_file, 'w', encoding='utf8') as f:
|
|
789
|
+
f.write(GeneralUtilities.get_metadata_for_file_for_clone_folder_structure(source_file))
|
|
790
|
+
else:
|
|
791
|
+
shutil.copyfile(source_file, target_file)
|
|
792
|
+
|
|
793
|
+
@staticmethod
|
|
794
|
+
@check_arguments
|
|
795
|
+
def current_user_has_elevated_privileges() -> bool:
|
|
796
|
+
try:
|
|
797
|
+
return os.getuid() == 0
|
|
798
|
+
except AttributeError:
|
|
799
|
+
return ctypes.windll.shell32.IsUserAnAdmin() == 1
|
|
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
|
+
|
|
807
|
+
@staticmethod
|
|
808
|
+
@check_arguments
|
|
809
|
+
def rename_names_of_all_files_and_folders(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
810
|
+
for file in GeneralUtilities.get_direct_files_of_folder(folder):
|
|
811
|
+
GeneralUtilities.replace_in_filename(file, replace_from, replace_to, replace_only_full_match)
|
|
812
|
+
for sub_folder in GeneralUtilities.get_direct_folders_of_folder(folder):
|
|
813
|
+
GeneralUtilities.rename_names_of_all_files_and_folders(sub_folder, replace_from, replace_to, replace_only_full_match)
|
|
814
|
+
GeneralUtilities.replace_in_foldername(folder, replace_from, replace_to, replace_only_full_match)
|
|
815
|
+
|
|
816
|
+
@staticmethod
|
|
817
|
+
@check_arguments
|
|
818
|
+
def get_direct_files_of_folder(folder: str) -> list[str]:
|
|
819
|
+
result = [os.path.join(folder, f) for f in listdir(folder) if isfile(join(folder, f))]
|
|
820
|
+
result = sorted(result, key=str.casefold)
|
|
821
|
+
return result
|
|
822
|
+
|
|
823
|
+
@staticmethod
|
|
824
|
+
@check_arguments
|
|
825
|
+
def get_direct_folders_of_folder(folder: str) -> list[str]:
|
|
826
|
+
result = [os.path.join(folder, f) for f in listdir(folder) if isdir(join(folder, f))]
|
|
827
|
+
result = sorted(result, key=str.casefold)
|
|
828
|
+
return result
|
|
829
|
+
|
|
830
|
+
@staticmethod
|
|
831
|
+
@check_arguments
|
|
832
|
+
def get_all_files_of_folder(folder: str) -> list[str]:
|
|
833
|
+
result = list()
|
|
834
|
+
result.extend(GeneralUtilities.get_direct_files_of_folder(folder))
|
|
835
|
+
for subfolder in GeneralUtilities.get_direct_folders_of_folder(folder):
|
|
836
|
+
result.extend(GeneralUtilities.get_all_files_of_folder(subfolder))
|
|
837
|
+
result = sorted(result, key=str.casefold)
|
|
838
|
+
return result
|
|
839
|
+
|
|
840
|
+
@staticmethod
|
|
841
|
+
@check_arguments
|
|
842
|
+
def get_all_folders_of_folder(folder: str) -> list[str]:
|
|
843
|
+
result = list()
|
|
844
|
+
subfolders = GeneralUtilities.get_direct_folders_of_folder(folder)
|
|
845
|
+
result.extend(subfolders)
|
|
846
|
+
for subfolder in subfolders:
|
|
847
|
+
result.extend(GeneralUtilities.get_all_folders_of_folder(subfolder))
|
|
848
|
+
result = sorted(result, key=str.casefold)
|
|
849
|
+
return result
|
|
850
|
+
|
|
851
|
+
@staticmethod
|
|
852
|
+
@check_arguments
|
|
853
|
+
def get_all_objects_of_folder(folder: str) -> list[str]:
|
|
854
|
+
return sorted(GeneralUtilities.get_all_files_of_folder(folder) + GeneralUtilities.get_all_folders_of_folder(folder), key=str.casefold)
|
|
855
|
+
|
|
856
|
+
@staticmethod
|
|
857
|
+
@check_arguments
|
|
858
|
+
def replace_in_filename(file: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
859
|
+
filename = Path(file).name
|
|
860
|
+
if (GeneralUtilities.__should_get_replaced(filename, replace_from, replace_only_full_match)):
|
|
861
|
+
folder_of_file = os.path.dirname(file)
|
|
862
|
+
os.rename(file, os.path.join(folder_of_file, filename.replace(replace_from, replace_to)))
|
|
863
|
+
|
|
864
|
+
@staticmethod
|
|
865
|
+
@check_arguments
|
|
866
|
+
def replace_in_foldername(folder: str, replace_from: str, replace_to: str, replace_only_full_match=False):
|
|
867
|
+
foldername = Path(folder).name
|
|
868
|
+
if (GeneralUtilities.__should_get_replaced(foldername, replace_from, replace_only_full_match)):
|
|
869
|
+
folder_of_folder = os.path.dirname(folder)
|
|
870
|
+
os.rename(folder, os.path.join(folder_of_folder, foldername.replace(replace_from, replace_to)))
|
|
871
|
+
|
|
872
|
+
@staticmethod
|
|
873
|
+
@check_arguments
|
|
874
|
+
def __should_get_replaced(input_text, search_text, replace_only_full_match) -> bool:
|
|
875
|
+
if replace_only_full_match:
|
|
876
|
+
return input_text == search_text
|
|
877
|
+
else:
|
|
878
|
+
return search_text in input_text
|
|
879
|
+
|
|
880
|
+
@staticmethod
|
|
881
|
+
@check_arguments
|
|
882
|
+
def str_none_safe(variable) -> str:
|
|
883
|
+
if variable is None:
|
|
884
|
+
return ''
|
|
885
|
+
else:
|
|
886
|
+
return str(variable)
|
|
887
|
+
|
|
888
|
+
@staticmethod
|
|
889
|
+
@check_arguments
|
|
890
|
+
def arguments_to_array(arguments_as_string: str) -> list[str]:
|
|
891
|
+
if arguments_as_string is None:
|
|
892
|
+
return []
|
|
893
|
+
if GeneralUtilities.string_has_content(arguments_as_string):
|
|
894
|
+
return arguments_as_string.split(" ") # TODO this function should get improved to allow whitespaces in quote-substrings
|
|
895
|
+
else:
|
|
896
|
+
return []
|
|
897
|
+
|
|
898
|
+
@staticmethod
|
|
899
|
+
@check_arguments
|
|
900
|
+
def arguments_to_array_for_log(arguments_as_string: str) -> list[str]:
|
|
901
|
+
if arguments_as_string is None:
|
|
902
|
+
return None
|
|
903
|
+
return GeneralUtilities.arguments_to_array(arguments_as_string)
|
|
904
|
+
|
|
905
|
+
@staticmethod
|
|
906
|
+
@check_arguments
|
|
907
|
+
def get_sha256_of_file(file: str) -> str:
|
|
908
|
+
sha256 = hashlib.sha256()
|
|
909
|
+
with open(file, "rb") as fileObject:
|
|
910
|
+
for chunk in iter(lambda: fileObject.read(4096), b""):
|
|
911
|
+
sha256.update(chunk)
|
|
912
|
+
return sha256.hexdigest()
|
|
913
|
+
|
|
914
|
+
@staticmethod
|
|
915
|
+
@check_arguments
|
|
916
|
+
def remove_duplicates(input_list) -> list:
|
|
917
|
+
result = []
|
|
918
|
+
for item in input_list:
|
|
919
|
+
if not item in result:
|
|
920
|
+
result.append(item)
|
|
921
|
+
return result
|
|
922
|
+
|
|
923
|
+
@staticmethod
|
|
924
|
+
@check_arguments
|
|
925
|
+
def print_stacktrace() -> None:
|
|
926
|
+
for line in traceback.format_stack():
|
|
927
|
+
GeneralUtilities.write_message_to_stdout(line.strip())
|
|
928
|
+
|
|
929
|
+
@staticmethod
|
|
930
|
+
@check_arguments
|
|
931
|
+
def string_to_boolean(value: str) -> bool:
|
|
932
|
+
value = value.strip().lower()
|
|
933
|
+
if value in ('yes', 'y', 'true', 't', '1'):
|
|
934
|
+
return True
|
|
935
|
+
elif value in ('no', 'n', 'false', 'f', '0'):
|
|
936
|
+
return False
|
|
937
|
+
else:
|
|
938
|
+
raise ValueError(f"Can not convert '{value}' to a boolean value")
|
|
939
|
+
|
|
940
|
+
@staticmethod
|
|
941
|
+
@check_arguments
|
|
942
|
+
def file_is_empty(file: str) -> bool:
|
|
943
|
+
return os.stat(file).st_size == 0
|
|
944
|
+
|
|
945
|
+
@staticmethod
|
|
946
|
+
@check_arguments
|
|
947
|
+
def folder_is_empty(folder: str) -> bool:
|
|
948
|
+
return len(GeneralUtilities.get_direct_files_of_folder(folder)) == 0 and len(GeneralUtilities.get_direct_folders_of_folder(folder)) == 0
|
|
949
|
+
|
|
950
|
+
@staticmethod
|
|
951
|
+
@check_arguments
|
|
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")
|
|
954
|
+
|
|
955
|
+
@staticmethod
|
|
956
|
+
@check_arguments
|
|
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()
|
|
964
|
+
return f"{name}_{GeneralUtilities.datetime_to_string_for_logfile_name(d)}"
|
|
965
|
+
|
|
966
|
+
@staticmethod
|
|
967
|
+
@check_arguments
|
|
968
|
+
def bytes_to_string(payload: bytes, encoding: str = 'utf-8') -> str:
|
|
969
|
+
return payload.decode(encoding, errors="ignore")
|
|
970
|
+
|
|
971
|
+
@staticmethod
|
|
972
|
+
@check_arguments
|
|
973
|
+
def string_to_bytes(payload: str, encoding: str = 'utf-8') -> bytes:
|
|
974
|
+
return payload.encode(encoding, errors="ignore")
|
|
975
|
+
|
|
976
|
+
@staticmethod
|
|
977
|
+
@check_arguments
|
|
978
|
+
def contains_line(lines, regex: str) -> bool:
|
|
979
|
+
for line in lines:
|
|
980
|
+
if (re.match(regex, line)):
|
|
981
|
+
return True
|
|
982
|
+
return False
|
|
983
|
+
|
|
984
|
+
@staticmethod
|
|
985
|
+
@check_arguments
|
|
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]]:
|
|
987
|
+
lines = GeneralUtilities.read_lines_from_file(file, encoding)
|
|
988
|
+
|
|
989
|
+
if ignore_first_line:
|
|
990
|
+
lines = lines[1:]
|
|
991
|
+
result = list()
|
|
992
|
+
line: str
|
|
993
|
+
for line_loopvariable in lines:
|
|
994
|
+
use_line = True
|
|
995
|
+
line = line_loopvariable
|
|
996
|
+
|
|
997
|
+
if trim_values:
|
|
998
|
+
line = line.strip()
|
|
999
|
+
if ignore_empty_lines:
|
|
1000
|
+
if not GeneralUtilities.string_has_content(line):
|
|
1001
|
+
use_line = False
|
|
1002
|
+
|
|
1003
|
+
if treat_number_sign_at_begin_of_line_as_comment:
|
|
1004
|
+
if line.startswith("#"):
|
|
1005
|
+
use_line = False
|
|
1006
|
+
|
|
1007
|
+
if use_line:
|
|
1008
|
+
if separator_character in line:
|
|
1009
|
+
raw_values_of_line = GeneralUtilities.to_list(line, separator_character)
|
|
1010
|
+
else:
|
|
1011
|
+
raw_values_of_line = [line]
|
|
1012
|
+
if trim_values:
|
|
1013
|
+
raw_values_of_line = [value.strip() for value in raw_values_of_line]
|
|
1014
|
+
values_of_line = []
|
|
1015
|
+
for raw_value_of_line in raw_values_of_line:
|
|
1016
|
+
value_of_line = raw_value_of_line
|
|
1017
|
+
if values_are_surrounded_by_quotes:
|
|
1018
|
+
value_of_line = value_of_line[1:]
|
|
1019
|
+
value_of_line = value_of_line[:-1]
|
|
1020
|
+
value_of_line = value_of_line.replace('""', '"')
|
|
1021
|
+
values_of_line.append(value_of_line)
|
|
1022
|
+
result.extend([values_of_line])
|
|
1023
|
+
return result
|
|
1024
|
+
|
|
1025
|
+
@staticmethod
|
|
1026
|
+
@check_arguments
|
|
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:
|
|
1033
|
+
try:
|
|
1034
|
+
return shutil.which(toolname) is not None
|
|
1035
|
+
except:
|
|
1036
|
+
return False
|
|
1037
|
+
|
|
1038
|
+
@staticmethod
|
|
1039
|
+
@check_arguments
|
|
1040
|
+
@deprecated
|
|
1041
|
+
def absolute_file_paths(directory: str) -> list[str]:
|
|
1042
|
+
return GeneralUtilities.get_all_files_of_folder(directory)
|
|
1043
|
+
|
|
1044
|
+
@staticmethod
|
|
1045
|
+
@check_arguments
|
|
1046
|
+
def to_list(list_as_string: str, separator: str = ",") -> list[str]:
|
|
1047
|
+
result = list()
|
|
1048
|
+
if list_as_string is not None:
|
|
1049
|
+
list_as_string = list_as_string.strip()
|
|
1050
|
+
if list_as_string == GeneralUtilities.empty_string:
|
|
1051
|
+
pass
|
|
1052
|
+
elif separator in list_as_string:
|
|
1053
|
+
for item in list_as_string.split(separator):
|
|
1054
|
+
result.append(item.strip())
|
|
1055
|
+
else:
|
|
1056
|
+
result.append(list_as_string)
|
|
1057
|
+
return result
|
|
1058
|
+
|
|
1059
|
+
@staticmethod
|
|
1060
|
+
@check_arguments
|
|
1061
|
+
def get_next_square_number(number: int) -> int:
|
|
1062
|
+
GeneralUtilities.assert_condition(number >= 0, "get_next_square_number is only applicable for nonnegative numbers")
|
|
1063
|
+
if number == 0:
|
|
1064
|
+
return 1
|
|
1065
|
+
root = 0
|
|
1066
|
+
square = 0
|
|
1067
|
+
while square < number:
|
|
1068
|
+
root = root+1
|
|
1069
|
+
square = root*root
|
|
1070
|
+
return root*root
|
|
1071
|
+
|
|
1072
|
+
@staticmethod
|
|
1073
|
+
@check_arguments
|
|
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."
|
|
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.")
|