multiCMD 1.33__tar.gz → 1.35__tar.gz
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.
- {multicmd-1.33 → multicmd-1.35}/PKG-INFO +1 -1
- {multicmd-1.33 → multicmd-1.35}/README.md +0 -0
- {multicmd-1.33 → multicmd-1.35}/multiCMD.egg-info/PKG-INFO +1 -1
- {multicmd-1.33 → multicmd-1.35}/multiCMD.py +358 -143
- {multicmd-1.33 → multicmd-1.35}/setup.py +0 -0
- {multicmd-1.33 → multicmd-1.35}/multiCMD.egg-info/SOURCES.txt +0 -0
- {multicmd-1.33 → multicmd-1.35}/multiCMD.egg-info/dependency_links.txt +0 -0
- {multicmd-1.33 → multicmd-1.35}/multiCMD.egg-info/entry_points.txt +0 -0
- {multicmd-1.33 → multicmd-1.35}/multiCMD.egg-info/requires.txt +0 -0
- {multicmd-1.33 → multicmd-1.35}/multiCMD.egg-info/top_level.txt +0 -0
- {multicmd-1.33 → multicmd-1.35}/setup.cfg +0 -0
File without changes
|
@@ -5,6 +5,7 @@
|
|
5
5
|
# "argparse",
|
6
6
|
# ]
|
7
7
|
# ///
|
8
|
+
#%% imports
|
8
9
|
import time
|
9
10
|
import threading
|
10
11
|
import io
|
@@ -18,11 +19,20 @@ import re
|
|
18
19
|
import itertools
|
19
20
|
import signal
|
20
21
|
|
21
|
-
|
22
|
+
#%% global vars
|
23
|
+
version = '1.35'
|
22
24
|
__version__ = version
|
23
|
-
|
25
|
+
COMMIT_DATE = '2025-09-10'
|
24
26
|
__running_threads = set()
|
25
27
|
__variables = {}
|
28
|
+
|
29
|
+
# immutable helpers compiled once at import time
|
30
|
+
_BRACKET_RX = re.compile(r'\[([^\]]+)\]')
|
31
|
+
_ALPHANUM = string.digits + string.ascii_letters
|
32
|
+
_ALPHA_IDX = {c: i for i, c in enumerate(_ALPHANUM)}
|
33
|
+
|
34
|
+
|
35
|
+
#%% objects
|
26
36
|
class Task:
|
27
37
|
def __init__(self, command):
|
28
38
|
self.command = command
|
@@ -41,7 +51,7 @@ class Task:
|
|
41
51
|
if self.thread is not None:
|
42
52
|
return self.thread.is_alive()
|
43
53
|
return False
|
44
|
-
|
54
|
+
|
45
55
|
class AsyncExecutor:
|
46
56
|
def __init__(self, max_threads=1,semaphore=...,timeout=0,quiet=True,dry_run=False,parse=False):
|
47
57
|
'''
|
@@ -227,11 +237,7 @@ class AsyncExecutor:
|
|
227
237
|
'''
|
228
238
|
return [task.returncode for task in self.tasks]
|
229
239
|
|
230
|
-
|
231
|
-
_BRACKET_RX = re.compile(r'\[([^\]]+)\]')
|
232
|
-
_ALPHANUM = string.digits + string.ascii_letters
|
233
|
-
_ALPHA_IDX = {c: i for i, c in enumerate(_ALPHANUM)}
|
234
|
-
|
240
|
+
#%% helper functions
|
235
241
|
def _expand_piece(piece, vars_):
|
236
242
|
"""Turn one comma-separated element from inside [...] into a list of strings."""
|
237
243
|
piece = piece.strip()
|
@@ -287,69 +293,6 @@ def _expand_ranges_fast(inStr):
|
|
287
293
|
# cartesian product of all segments
|
288
294
|
return [''.join(parts) for parts in itertools.product(*segments)]
|
289
295
|
|
290
|
-
def _expand_ranges(inStr):
|
291
|
-
'''
|
292
|
-
Expand ranges in a string
|
293
|
-
|
294
|
-
@params:
|
295
|
-
inStr: The string to expand
|
296
|
-
|
297
|
-
@returns:
|
298
|
-
list[str]: The expanded string
|
299
|
-
'''
|
300
|
-
global __variables
|
301
|
-
expandingStr = [inStr]
|
302
|
-
expandedList = []
|
303
|
-
# all valid alphanumeric characters
|
304
|
-
alphanumeric = string.digits + string.ascii_letters
|
305
|
-
while len(expandingStr) > 0:
|
306
|
-
currentStr = expandingStr.pop()
|
307
|
-
match = re.search(r'\[(.*?)]', currentStr)
|
308
|
-
if not match:
|
309
|
-
expandedList.append(currentStr)
|
310
|
-
continue
|
311
|
-
group = match.group(1)
|
312
|
-
parts = group.split(',')
|
313
|
-
for part in parts:
|
314
|
-
part = part.strip()
|
315
|
-
if ':' in part:
|
316
|
-
variableName, _, part = part.partition(':')
|
317
|
-
__variables[variableName] = part
|
318
|
-
expandingStr.append(currentStr.replace(match.group(0), '', 1))
|
319
|
-
elif '-' in part:
|
320
|
-
try:
|
321
|
-
range_start,_, range_end = part.partition('-')
|
322
|
-
except ValueError:
|
323
|
-
expandedList.append(currentStr)
|
324
|
-
continue
|
325
|
-
range_start = range_start.strip()
|
326
|
-
if range_start in __variables:
|
327
|
-
range_start = __variables[range_start]
|
328
|
-
range_end = range_end.strip()
|
329
|
-
if range_end in __variables:
|
330
|
-
range_end = __variables[range_end]
|
331
|
-
if range_start.isdigit() and range_end.isdigit():
|
332
|
-
padding_length = min(len(range_start), len(range_end))
|
333
|
-
format_str = "{:0" + str(padding_length) + "d}"
|
334
|
-
for i in range(int(range_start), int(range_end) + 1):
|
335
|
-
formatted_i = format_str.format(i)
|
336
|
-
expandingStr.append(currentStr.replace(match.group(0), formatted_i, 1))
|
337
|
-
elif all(c in string.hexdigits for c in range_start + range_end):
|
338
|
-
for i in range(int(range_start, 16), int(range_end, 16) + 1):
|
339
|
-
expandingStr.append(currentStr.replace(match.group(0), format(i, 'x'), 1))
|
340
|
-
else:
|
341
|
-
try:
|
342
|
-
start_index = alphanumeric.index(range_start)
|
343
|
-
end_index = alphanumeric.index(range_end)
|
344
|
-
for i in range(start_index, end_index + 1):
|
345
|
-
expandingStr.append(currentStr.replace(match.group(0), alphanumeric[i], 1))
|
346
|
-
except ValueError:
|
347
|
-
expandedList.append(currentStr)
|
348
|
-
else:
|
349
|
-
expandingStr.append(currentStr.replace(match.group(0), part, 1))
|
350
|
-
expandedList.reverse()
|
351
|
-
return expandedList
|
352
|
-
|
353
296
|
def __handle_stream(stream,target,pre='',post='',quiet=False):
|
354
297
|
'''
|
355
298
|
Handle a stream
|
@@ -519,6 +462,43 @@ def __run_command(task,sem, timeout=60, quiet=False,dry_run=False,with_stdErr=Fa
|
|
519
462
|
else:
|
520
463
|
return task.stdout
|
521
464
|
|
465
|
+
def __format_command(command,expand = False):
|
466
|
+
'''
|
467
|
+
Format a command
|
468
|
+
|
469
|
+
@params:
|
470
|
+
command: The command to format
|
471
|
+
expand: Whether to expand ranges
|
472
|
+
|
473
|
+
@returns:
|
474
|
+
list[list[str]]: The formatted commands sequence
|
475
|
+
'''
|
476
|
+
if isinstance(command,str):
|
477
|
+
if expand:
|
478
|
+
commands = _expand_ranges_fast(command)
|
479
|
+
else:
|
480
|
+
commands = [command]
|
481
|
+
return [command.split() for command in commands]
|
482
|
+
# elif it is a iterable
|
483
|
+
elif hasattr(command,'__iter__'):
|
484
|
+
sanitized_command = []
|
485
|
+
for field in command:
|
486
|
+
if isinstance(field,str):
|
487
|
+
sanitized_command.append(field)
|
488
|
+
else:
|
489
|
+
sanitized_command.append(repr(field))
|
490
|
+
if not expand:
|
491
|
+
return [sanitized_command]
|
492
|
+
sanitized_expanded_command = [_expand_ranges_fast(field) for field in sanitized_command]
|
493
|
+
# now the command had been expanded to list of list of fields with each field expanded to all possible options
|
494
|
+
# we need to generate all possible combinations of the fields
|
495
|
+
# we will use the cartesian product to do this
|
496
|
+
commands = list(itertools.product(*sanitized_expanded_command))
|
497
|
+
return [list(command) for command in commands]
|
498
|
+
else:
|
499
|
+
return __format_command(str(command),expand=expand)
|
500
|
+
|
501
|
+
#%% core funcitons
|
522
502
|
def ping(hosts,timeout=1,max_threads=0,quiet=True,dry_run=False,with_stdErr=False,
|
523
503
|
return_code_only=False,return_object=False,wait_for_return=True,return_true_false=True):
|
524
504
|
'''
|
@@ -560,7 +540,6 @@ def ping(hosts,timeout=1,max_threads=0,quiet=True,dry_run=False,with_stdErr=Fals
|
|
560
540
|
else:
|
561
541
|
return results
|
562
542
|
|
563
|
-
|
564
543
|
def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
|
565
544
|
return_code_only=False,return_object=False,wait_for_return=True, sem = None):
|
566
545
|
'''
|
@@ -585,42 +564,6 @@ def run_command(command, timeout=0,max_threads=1,quiet=False,dry_run=False,with_
|
|
585
564
|
dry_run=dry_run, with_stdErr=with_stdErr, return_code_only=return_code_only,
|
586
565
|
return_object=return_object,parse=False,wait_for_return=wait_for_return,sem=sem)[0]
|
587
566
|
|
588
|
-
def __format_command(command,expand = False):
|
589
|
-
'''
|
590
|
-
Format a command
|
591
|
-
|
592
|
-
@params:
|
593
|
-
command: The command to format
|
594
|
-
expand: Whether to expand ranges
|
595
|
-
|
596
|
-
@returns:
|
597
|
-
list[list[str]]: The formatted commands sequence
|
598
|
-
'''
|
599
|
-
if isinstance(command,str):
|
600
|
-
if expand:
|
601
|
-
commands = _expand_ranges_fast(command)
|
602
|
-
else:
|
603
|
-
commands = [command]
|
604
|
-
return [command.split() for command in commands]
|
605
|
-
# elif it is a iterable
|
606
|
-
elif hasattr(command,'__iter__'):
|
607
|
-
sanitized_command = []
|
608
|
-
for field in command:
|
609
|
-
if isinstance(field,str):
|
610
|
-
sanitized_command.append(field)
|
611
|
-
else:
|
612
|
-
sanitized_command.append(repr(field))
|
613
|
-
if not expand:
|
614
|
-
return [sanitized_command]
|
615
|
-
sanitized_expanded_command = [_expand_ranges_fast(field) for field in sanitized_command]
|
616
|
-
# now the command had been expanded to list of list of fields with each field expanded to all possible options
|
617
|
-
# we need to generate all possible combinations of the fields
|
618
|
-
# we will use the cartesian product to do this
|
619
|
-
commands = list(itertools.product(*sanitized_expanded_command))
|
620
|
-
return [list(command) for command in commands]
|
621
|
-
else:
|
622
|
-
return __format_command(str(command),expand=expand)
|
623
|
-
|
624
567
|
def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,with_stdErr=False,
|
625
568
|
return_code_only=False,return_object=False, parse = False, wait_for_return = True, sem : threading.Semaphore = None):
|
626
569
|
'''
|
@@ -695,6 +638,21 @@ def join_threads(threads=__running_threads,timeout=None):
|
|
695
638
|
if threads is __running_threads:
|
696
639
|
__running_threads = {t for t in threads if t.is_alive()}
|
697
640
|
|
641
|
+
def main():
|
642
|
+
parser = argparse.ArgumentParser(description='Run multiple commands in parallel')
|
643
|
+
parser.add_argument('commands', metavar='command', type=str, nargs='+',help='commands to run')
|
644
|
+
parser.add_argument('-p','--parse', action='store_true',help='Parse ranged input and expand them into multiple commands')
|
645
|
+
parser.add_argument('-t','--timeout', metavar='timeout', type=int, default=60,help='timeout for each command')
|
646
|
+
parser.add_argument('-m','--max_threads', metavar='max_threads', type=int, default=1,help='maximum number of threads to use')
|
647
|
+
parser.add_argument('-q','--quiet', action='store_true',help='quiet mode')
|
648
|
+
parser.add_argument('-V','--version', action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} by pan@zopyr.us')
|
649
|
+
args = parser.parse_args()
|
650
|
+
run_commands(args.commands, args.timeout, args.max_threads, args.quiet,parse = args.parse, with_stdErr=True)
|
651
|
+
|
652
|
+
if __name__ == '__main__':
|
653
|
+
main()
|
654
|
+
|
655
|
+
#%% misc functions
|
698
656
|
def input_with_timeout_and_countdown(timeout, prompt='Please enter your selection'):
|
699
657
|
"""
|
700
658
|
Read an input from the user with a timeout and a countdown.
|
@@ -721,6 +679,194 @@ def input_with_timeout_and_countdown(timeout, prompt='Please enter your selectio
|
|
721
679
|
# If there is no input, return None
|
722
680
|
return None
|
723
681
|
|
682
|
+
def pretty_format_table(data, delimiter="\t", header=None, full=False):
|
683
|
+
import re
|
684
|
+
version = 1.12
|
685
|
+
_ = version
|
686
|
+
def visible_len(s):
|
687
|
+
return len(re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", s))
|
688
|
+
def table_width(col_widths, sep_len):
|
689
|
+
# total width = sum of column widths + separators between columns
|
690
|
+
return sum(col_widths) + sep_len * (len(col_widths) - 1)
|
691
|
+
def truncate_to_width(s, width):
|
692
|
+
# If fits, leave as is. If too long and width >= 1, keep width-1 chars + "."
|
693
|
+
# If width == 0, nothing fits; return empty string.
|
694
|
+
if visible_len(s) <= width:
|
695
|
+
return s
|
696
|
+
if width <= 0:
|
697
|
+
return ""
|
698
|
+
# Build a truncated plain string based on visible chars (no ANSI awareness for slicing)
|
699
|
+
# For simplicity, slice the raw string. This may cut ANSI; best to avoid ANSI in data if truncation occurs.
|
700
|
+
return s[: max(width - 2, 0)] + ".."
|
701
|
+
if not data:
|
702
|
+
return ""
|
703
|
+
# Normalize input data structure
|
704
|
+
if isinstance(data, str):
|
705
|
+
data = data.strip("\n").split("\n")
|
706
|
+
data = [line.split(delimiter) for line in data]
|
707
|
+
elif isinstance(data, dict):
|
708
|
+
if isinstance(next(iter(data.values())), dict):
|
709
|
+
tempData = [["key"] + list(next(iter(data.values())).keys())]
|
710
|
+
tempData.extend([[key] + list(value.values()) for key, value in data.items()])
|
711
|
+
data = tempData
|
712
|
+
else:
|
713
|
+
data = [[key] + list(value) for key, value in data.items()]
|
714
|
+
elif not isinstance(data, list):
|
715
|
+
data = list(data)
|
716
|
+
if isinstance(data[0], dict):
|
717
|
+
tempData = [list(data[0].keys())]
|
718
|
+
tempData.extend([list(item.values()) for item in data])
|
719
|
+
data = tempData
|
720
|
+
data = [[str(item) for item in row] for row in data]
|
721
|
+
num_cols = len(data[0])
|
722
|
+
# Resolve header and rows
|
723
|
+
using_provided_header = header is not None
|
724
|
+
if not using_provided_header:
|
725
|
+
header = data[0]
|
726
|
+
rows = data[1:]
|
727
|
+
else:
|
728
|
+
if isinstance(header, str):
|
729
|
+
header = header.split(delimiter)
|
730
|
+
# Pad/trim header to match num_cols
|
731
|
+
if len(header) < num_cols:
|
732
|
+
header = header + [""] * (num_cols - len(header))
|
733
|
+
elif len(header) > num_cols:
|
734
|
+
header = header[:num_cols]
|
735
|
+
rows = data
|
736
|
+
# Compute initial column widths based on data and header
|
737
|
+
def compute_col_widths(hdr, rows_):
|
738
|
+
col_w = [0] * len(hdr)
|
739
|
+
for i in range(len(hdr)):
|
740
|
+
col_w[i] = max(visible_len(hdr[i]), *(visible_len(r[i]) for r in rows_ if i < len(r)))
|
741
|
+
return col_w
|
742
|
+
# Ensure all rows have the same number of columns
|
743
|
+
normalized_rows = []
|
744
|
+
for r in rows:
|
745
|
+
if len(r) < num_cols:
|
746
|
+
r = r + [""] * (num_cols - len(r))
|
747
|
+
elif len(r) > num_cols:
|
748
|
+
r = r[:num_cols]
|
749
|
+
normalized_rows.append(r)
|
750
|
+
rows = normalized_rows
|
751
|
+
col_widths = compute_col_widths(header, rows)
|
752
|
+
# If full=True, keep existing formatting
|
753
|
+
# Else try to fit within the terminal width by:
|
754
|
+
# 1) Switching to compressed separators if needed
|
755
|
+
# 2) Recursively compressing columns (truncating with ".")
|
756
|
+
sep = " | "
|
757
|
+
hsep = "-+-"
|
758
|
+
cols = get_terminal_size()[0]
|
759
|
+
def render(hdr, rows, col_w, sep_str, hsep_str):
|
760
|
+
row_fmt = sep_str.join("{{:<{}}}".format(w) for w in col_w)
|
761
|
+
out = []
|
762
|
+
out.append(row_fmt.format(*hdr))
|
763
|
+
out.append(hsep_str.join("-" * w for w in col_w))
|
764
|
+
for row in rows:
|
765
|
+
if not any(row):
|
766
|
+
out.append(hsep_str.join("-" * w for w in col_w))
|
767
|
+
else:
|
768
|
+
row = [truncate_to_width(row[i], col_w[i]) for i in range(len(row))]
|
769
|
+
out.append(row_fmt.format(*row))
|
770
|
+
return "\n".join(out) + "\n"
|
771
|
+
if full:
|
772
|
+
return render(header, rows, col_widths, sep, hsep)
|
773
|
+
# Try default separators first
|
774
|
+
if table_width(col_widths, len(sep)) <= cols:
|
775
|
+
return render(header, rows, col_widths, sep, hsep)
|
776
|
+
# Use compressed separators (no spaces)
|
777
|
+
sep = "|"
|
778
|
+
hsep = "+"
|
779
|
+
if table_width(col_widths, len(sep)) <= cols:
|
780
|
+
return render(header, rows, col_widths, sep, hsep)
|
781
|
+
# Begin column compression
|
782
|
+
# Track which columns have been compressed already to header width
|
783
|
+
header_widths = [visible_len(h) for h in header]
|
784
|
+
width_diff = [max(col_widths[i] - header_widths[i],0) for i in range(num_cols)]
|
785
|
+
total_overflow_width = table_width(col_widths, len(sep)) - cols
|
786
|
+
for i, diff in sorted(enumerate(width_diff), key=lambda x: -x[1]):
|
787
|
+
if total_overflow_width <= 0:
|
788
|
+
break
|
789
|
+
if diff <= 0:
|
790
|
+
continue
|
791
|
+
reduce_by = min(diff, total_overflow_width)
|
792
|
+
col_widths[i] -= reduce_by
|
793
|
+
total_overflow_width -= reduce_by
|
794
|
+
return render(header, rows, col_widths, sep, hsep)
|
795
|
+
|
796
|
+
def parseTable(data,sort=False):
|
797
|
+
if isinstance(data, str):
|
798
|
+
data = data.strip('\n').split('\n')
|
799
|
+
header_line = data[0]
|
800
|
+
# Use regex to find column names and their positions
|
801
|
+
pattern = r'(\S(?:.*?\S)?)(?=\s{2,}|\s*$)'
|
802
|
+
matches = list(re.finditer(pattern, header_line))
|
803
|
+
data_list = [[]]
|
804
|
+
columns = []
|
805
|
+
for i, match in enumerate(matches):
|
806
|
+
col_name = match.group(1)
|
807
|
+
data_list[0].append(col_name)
|
808
|
+
start = match.start()
|
809
|
+
if i + 1 < len(matches):
|
810
|
+
end = matches[i+1].start()
|
811
|
+
else:
|
812
|
+
end = None # Last column goes till the end
|
813
|
+
columns.append((col_name, start, end))
|
814
|
+
for line in data[1:]:
|
815
|
+
if not line.strip():
|
816
|
+
continue # Skip empty lines
|
817
|
+
row = []
|
818
|
+
for col_name, start, end in columns:
|
819
|
+
if end is not None:
|
820
|
+
value = line[start:end].strip()
|
821
|
+
else:
|
822
|
+
value = line[start:].strip()
|
823
|
+
row.append(value)
|
824
|
+
data_list.append(row)
|
825
|
+
# sort data_list[1:] by the first column
|
826
|
+
if sort:
|
827
|
+
data_list[1:] = sorted(data_list[1:], key=lambda x: x[0])
|
828
|
+
return data_list
|
829
|
+
|
830
|
+
def slugify(value, allow_unicode=False):
|
831
|
+
import unicodedata
|
832
|
+
"""
|
833
|
+
Taken from https://github.com/django/django/blob/master/django/utils/text.py
|
834
|
+
Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
|
835
|
+
dashes to single dashes. Remove characters that aren't alphanumerics,
|
836
|
+
underscores, or hyphens. Convert to lowercase. Also strip leading and
|
837
|
+
trailing whitespace, dashes, and underscores.
|
838
|
+
"""
|
839
|
+
value = str(value)
|
840
|
+
if allow_unicode:
|
841
|
+
value = unicodedata.normalize('NFKC', value)
|
842
|
+
else:
|
843
|
+
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
|
844
|
+
value = re.sub(r'[^\w\s-]', '', value.lower())
|
845
|
+
return re.sub(r'[-\s]+', '-', value).strip('-_')
|
846
|
+
|
847
|
+
def get_terminal_size():
|
848
|
+
'''
|
849
|
+
Get the terminal size
|
850
|
+
|
851
|
+
@params:
|
852
|
+
None
|
853
|
+
|
854
|
+
@returns:
|
855
|
+
(int,int): the number of columns and rows of the terminal
|
856
|
+
'''
|
857
|
+
try:
|
858
|
+
import os
|
859
|
+
_tsize = os.get_terminal_size()
|
860
|
+
except:
|
861
|
+
try:
|
862
|
+
import fcntl, termios, struct
|
863
|
+
packed = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))
|
864
|
+
_tsize = struct.unpack('HHHH', packed)[:2]
|
865
|
+
except:
|
866
|
+
import shutil
|
867
|
+
_tsize = shutil.get_terminal_size(fallback=(120, 30))
|
868
|
+
return _tsize
|
869
|
+
|
724
870
|
def _genrate_progress_bar(iteration, total, prefix='', suffix='',columns=120):
|
725
871
|
'''
|
726
872
|
Generate a progress bar string
|
@@ -780,29 +926,6 @@ def _genrate_progress_bar(iteration, total, prefix='', suffix='',columns=120):
|
|
780
926
|
lineOut += suffix
|
781
927
|
return lineOut
|
782
928
|
|
783
|
-
def get_terminal_size():
|
784
|
-
'''
|
785
|
-
Get the terminal size
|
786
|
-
|
787
|
-
@params:
|
788
|
-
None
|
789
|
-
|
790
|
-
@returns:
|
791
|
-
(int,int): the number of columns and rows of the terminal
|
792
|
-
'''
|
793
|
-
try:
|
794
|
-
import os
|
795
|
-
_tsize = os.get_terminal_size()
|
796
|
-
except:
|
797
|
-
try:
|
798
|
-
import fcntl, termios, struct
|
799
|
-
packed = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))
|
800
|
-
_tsize = struct.unpack('HHHH', packed)[:2]
|
801
|
-
except:
|
802
|
-
import shutil
|
803
|
-
_tsize = shutil.get_terminal_size(fallback=(120, 30))
|
804
|
-
return _tsize
|
805
|
-
|
806
929
|
def print_progress_bar(iteration, total, prefix='', suffix=''):
|
807
930
|
'''
|
808
931
|
Call in a loop to create terminal progress bar
|
@@ -827,16 +950,108 @@ def print_progress_bar(iteration, total, prefix='', suffix=''):
|
|
827
950
|
if iteration % 5 == 0:
|
828
951
|
print(_genrate_progress_bar(iteration, total, prefix, suffix))
|
829
952
|
|
830
|
-
def
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
953
|
+
def format_bytes(size, use_1024_bytes=None, to_int=False, to_str=False, str_format=".2f"):
|
954
|
+
if to_int or isinstance(size, str):
|
955
|
+
if isinstance(size, int):
|
956
|
+
return size
|
957
|
+
elif isinstance(size, str):
|
958
|
+
match = re.match(r"(\d+(\.\d+)?)\s*([a-zA-Z]*)", size)
|
959
|
+
if not match:
|
960
|
+
if to_str:
|
961
|
+
return size
|
962
|
+
print(
|
963
|
+
"Invalid size format. Expected format: 'number [unit]', "
|
964
|
+
"e.g., '1.5 GiB' or '1.5GiB'"
|
965
|
+
)
|
966
|
+
print(f"Got: {size}")
|
967
|
+
return 0
|
968
|
+
number, _, unit = match.groups()
|
969
|
+
number = float(number)
|
970
|
+
unit = unit.strip().lower().rstrip("b")
|
971
|
+
if unit.endswith("i"):
|
972
|
+
use_1024_bytes = True
|
973
|
+
elif use_1024_bytes is None:
|
974
|
+
use_1024_bytes = False
|
975
|
+
unit = unit.rstrip("i")
|
976
|
+
if use_1024_bytes:
|
977
|
+
power = 2**10
|
978
|
+
else:
|
979
|
+
power = 10**3
|
980
|
+
unit_labels = {
|
981
|
+
"": 0,
|
982
|
+
"k": 1,
|
983
|
+
"m": 2,
|
984
|
+
"g": 3,
|
985
|
+
"t": 4,
|
986
|
+
"p": 5,
|
987
|
+
"e": 6,
|
988
|
+
"z": 7,
|
989
|
+
"y": 8,
|
990
|
+
}
|
991
|
+
if unit not in unit_labels:
|
992
|
+
if to_str:
|
993
|
+
return size
|
994
|
+
else:
|
995
|
+
if to_str:
|
996
|
+
return format_bytes(
|
997
|
+
size=int(number * (power**unit_labels[unit])),
|
998
|
+
use_1024_bytes=use_1024_bytes,
|
999
|
+
to_str=True,
|
1000
|
+
str_format=str_format,
|
1001
|
+
)
|
1002
|
+
return int(number * (power**unit_labels[unit]))
|
1003
|
+
else:
|
1004
|
+
try:
|
1005
|
+
return int(size)
|
1006
|
+
except Exception:
|
1007
|
+
return 0
|
1008
|
+
elif to_str or isinstance(size, int) or isinstance(size, float):
|
1009
|
+
if isinstance(size, str):
|
1010
|
+
try:
|
1011
|
+
size = size.rstrip("B").rstrip("b")
|
1012
|
+
size = float(size.lower().strip())
|
1013
|
+
except Exception:
|
1014
|
+
return size
|
1015
|
+
if use_1024_bytes or use_1024_bytes is None:
|
1016
|
+
power = 2**10
|
1017
|
+
n = 0
|
1018
|
+
power_labels = {
|
1019
|
+
0: "",
|
1020
|
+
1: "Ki",
|
1021
|
+
2: "Mi",
|
1022
|
+
3: "Gi",
|
1023
|
+
4: "Ti",
|
1024
|
+
5: "Pi",
|
1025
|
+
6: "Ei",
|
1026
|
+
7: "Zi",
|
1027
|
+
8: "Yi",
|
1028
|
+
}
|
1029
|
+
while size > power:
|
1030
|
+
size /= power
|
1031
|
+
n += 1
|
1032
|
+
return f"{size:{str_format}} {' '}{power_labels[n]}".replace(" ", " ")
|
1033
|
+
else:
|
1034
|
+
power = 10**3
|
1035
|
+
n = 0
|
1036
|
+
power_labels = {
|
1037
|
+
0: "",
|
1038
|
+
1: "K",
|
1039
|
+
2: "M",
|
1040
|
+
3: "G",
|
1041
|
+
4: "T",
|
1042
|
+
5: "P",
|
1043
|
+
6: "E",
|
1044
|
+
7: "Z",
|
1045
|
+
8: "Y",
|
1046
|
+
}
|
1047
|
+
while size > power:
|
1048
|
+
size /= power
|
1049
|
+
n += 1
|
1050
|
+
return f"{size:{str_format}} {' '}{power_labels[n]}".replace(" ", " ")
|
1051
|
+
else:
|
1052
|
+
try:
|
1053
|
+
return format_bytes(float(size), use_1024_bytes)
|
1054
|
+
except Exception:
|
1055
|
+
pass
|
1056
|
+
return 0
|
840
1057
|
|
841
|
-
if __name__ == '__main__':
|
842
|
-
main()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|