multiCMD 1.34__py3-none-any.whl → 1.37__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.
- multiCMD.py +200 -46
- {multicmd-1.34.dist-info → multicmd-1.37.dist-info}/METADATA +1 -1
- multicmd-1.37.dist-info/RECORD +6 -0
- multicmd-1.34.dist-info/RECORD +0 -6
- {multicmd-1.34.dist-info → multicmd-1.37.dist-info}/WHEEL +0 -0
- {multicmd-1.34.dist-info → multicmd-1.37.dist-info}/entry_points.txt +0 -0
- {multicmd-1.34.dist-info → multicmd-1.37.dist-info}/top_level.txt +0 -0
multiCMD.py
CHANGED
@@ -20,9 +20,9 @@ import itertools
|
|
20
20
|
import signal
|
21
21
|
|
22
22
|
#%% global vars
|
23
|
-
version = '1.
|
23
|
+
version = '1.37'
|
24
24
|
__version__ = version
|
25
|
-
COMMIT_DATE = '2025-
|
25
|
+
COMMIT_DATE = '2025-09-11'
|
26
26
|
__running_threads = set()
|
27
27
|
__variables = {}
|
28
28
|
|
@@ -621,7 +621,7 @@ def run_commands(commands, timeout=0,max_threads=1,quiet=False,dry_run=False,wit
|
|
621
621
|
else:
|
622
622
|
return [task.stdout for task in tasks]
|
623
623
|
|
624
|
-
def join_threads(threads
|
624
|
+
def join_threads(threads=...,timeout=None):
|
625
625
|
'''
|
626
626
|
Join threads
|
627
627
|
|
@@ -633,6 +633,8 @@ def join_threads(threads=__running_threads,timeout=None):
|
|
633
633
|
None
|
634
634
|
'''
|
635
635
|
global __running_threads
|
636
|
+
if threads is ...:
|
637
|
+
threads = __running_threads
|
636
638
|
for thread in threads:
|
637
639
|
thread.join(timeout=timeout)
|
638
640
|
if threads is __running_threads:
|
@@ -679,73 +681,119 @@ def input_with_timeout_and_countdown(timeout, prompt='Please enter your selectio
|
|
679
681
|
# If there is no input, return None
|
680
682
|
return None
|
681
683
|
|
682
|
-
def pretty_format_table(data, delimiter
|
683
|
-
|
684
|
+
def pretty_format_table(data, delimiter="\t", header=None, full=False):
|
685
|
+
import re
|
686
|
+
version = 1.12
|
684
687
|
_ = version
|
688
|
+
def visible_len(s):
|
689
|
+
return len(re.sub(r"\x1b\[[0-?]*[ -/]*[@-~]", "", s))
|
690
|
+
def table_width(col_widths, sep_len):
|
691
|
+
# total width = sum of column widths + separators between columns
|
692
|
+
return sum(col_widths) + sep_len * (len(col_widths) - 1)
|
693
|
+
def truncate_to_width(s, width):
|
694
|
+
# If fits, leave as is. If too long and width >= 1, keep width-1 chars + "."
|
695
|
+
# If width == 0, nothing fits; return empty string.
|
696
|
+
if visible_len(s) <= width:
|
697
|
+
return s
|
698
|
+
if width <= 0:
|
699
|
+
return ""
|
700
|
+
# Build a truncated plain string based on visible chars (no ANSI awareness for slicing)
|
701
|
+
# For simplicity, slice the raw string. This may cut ANSI; best to avoid ANSI in data if truncation occurs.
|
702
|
+
return s[: max(width - 2, 0)] + ".."
|
685
703
|
if not data:
|
686
|
-
return
|
704
|
+
return ""
|
705
|
+
# Normalize input data structure
|
687
706
|
if isinstance(data, str):
|
688
|
-
data = data.strip(
|
707
|
+
data = data.strip("\n").split("\n")
|
689
708
|
data = [line.split(delimiter) for line in data]
|
690
709
|
elif isinstance(data, dict):
|
691
|
-
# flatten the 2D dict to a list of lists
|
692
710
|
if isinstance(next(iter(data.values())), dict):
|
693
|
-
tempData = [[
|
694
|
-
tempData.extend(
|
711
|
+
tempData = [["key"] + list(next(iter(data.values())).keys())]
|
712
|
+
tempData.extend([[key] + list(value.values()) for key, value in data.items()])
|
695
713
|
data = tempData
|
696
714
|
else:
|
697
|
-
# it is a dict of lists
|
698
715
|
data = [[key] + list(value) for key, value in data.items()]
|
699
716
|
elif not isinstance(data, list):
|
700
717
|
data = list(data)
|
701
|
-
# format the list into 2d list of list of strings
|
702
718
|
if isinstance(data[0], dict):
|
703
|
-
tempData = [data[0].keys()]
|
719
|
+
tempData = [list(data[0].keys())]
|
704
720
|
tempData.extend([list(item.values()) for item in data])
|
705
721
|
data = tempData
|
706
722
|
data = [[str(item) for item in row] for row in data]
|
707
723
|
num_cols = len(data[0])
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
#col_widths[c] = max(len(row[c]) for row in data)
|
712
|
-
# handle ansii escape sequences
|
713
|
-
col_widths[c] = max(len(re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]','',row[c])) for row in data)
|
714
|
-
if header:
|
715
|
-
header_widths = [len(re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]', '', col)) for col in header]
|
716
|
-
col_widths = [max(col_widths[i], header_widths[i]) for i in range(num_cols)]
|
717
|
-
# Build the row format string
|
718
|
-
row_format = ' | '.join('{{:<{}}}'.format(width) for width in col_widths)
|
719
|
-
# Print the header
|
720
|
-
if not header:
|
724
|
+
# Resolve header and rows
|
725
|
+
using_provided_header = header is not None
|
726
|
+
if not using_provided_header:
|
721
727
|
header = data[0]
|
722
|
-
|
723
|
-
outTable.append(row_format.format(*header))
|
724
|
-
outTable.append('-+-'.join('-' * width for width in col_widths))
|
725
|
-
for row in data[1:]:
|
726
|
-
# if the row is empty, print an divider
|
727
|
-
if not any(row):
|
728
|
-
outTable.append('-+-'.join('-' * width for width in col_widths))
|
729
|
-
else:
|
730
|
-
outTable.append(row_format.format(*row))
|
728
|
+
rows = data[1:]
|
731
729
|
else:
|
732
|
-
|
733
|
-
if isinstance(header,str):
|
730
|
+
if isinstance(header, str):
|
734
731
|
header = header.split(delimiter)
|
732
|
+
# Pad/trim header to match num_cols
|
735
733
|
if len(header) < num_cols:
|
736
|
-
header
|
734
|
+
header = header + [""] * (num_cols - len(header))
|
737
735
|
elif len(header) > num_cols:
|
738
736
|
header = header[:num_cols]
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
737
|
+
rows = data
|
738
|
+
# Compute initial column widths based on data and header
|
739
|
+
def compute_col_widths(hdr, rows_):
|
740
|
+
col_w = [0] * len(hdr)
|
741
|
+
for i in range(len(hdr)):
|
742
|
+
col_w[i] = max(0, visible_len(hdr[i]), *(visible_len(r[i]) for r in rows_ if i < len(r)))
|
743
|
+
return col_w
|
744
|
+
# Ensure all rows have the same number of columns
|
745
|
+
normalized_rows = []
|
746
|
+
for r in rows:
|
747
|
+
if len(r) < num_cols:
|
748
|
+
r = r + [""] * (num_cols - len(r))
|
749
|
+
elif len(r) > num_cols:
|
750
|
+
r = r[:num_cols]
|
751
|
+
normalized_rows.append(r)
|
752
|
+
rows = normalized_rows
|
753
|
+
col_widths = compute_col_widths(header, rows)
|
754
|
+
# If full=True, keep existing formatting
|
755
|
+
# Else try to fit within the terminal width by:
|
756
|
+
# 1) Switching to compressed separators if needed
|
757
|
+
# 2) Recursively compressing columns (truncating with ".")
|
758
|
+
sep = " | "
|
759
|
+
hsep = "-+-"
|
760
|
+
cols = get_terminal_size()[0]
|
761
|
+
def render(hdr, rows, col_w, sep_str, hsep_str):
|
762
|
+
row_fmt = sep_str.join("{{:<{}}}".format(w) for w in col_w)
|
763
|
+
out = []
|
764
|
+
out.append(row_fmt.format(*hdr))
|
765
|
+
out.append(hsep_str.join("-" * w for w in col_w))
|
766
|
+
for row in rows:
|
744
767
|
if not any(row):
|
745
|
-
|
768
|
+
out.append(hsep_str.join("-" * w for w in col_w))
|
746
769
|
else:
|
747
|
-
|
748
|
-
|
770
|
+
row = [truncate_to_width(row[i], col_w[i]) for i in range(len(row))]
|
771
|
+
out.append(row_fmt.format(*row))
|
772
|
+
return "\n".join(out) + "\n"
|
773
|
+
if full:
|
774
|
+
return render(header, rows, col_widths, sep, hsep)
|
775
|
+
# Try default separators first
|
776
|
+
if table_width(col_widths, len(sep)) <= cols:
|
777
|
+
return render(header, rows, col_widths, sep, hsep)
|
778
|
+
# Use compressed separators (no spaces)
|
779
|
+
sep = "|"
|
780
|
+
hsep = "+"
|
781
|
+
if table_width(col_widths, len(sep)) <= cols:
|
782
|
+
return render(header, rows, col_widths, sep, hsep)
|
783
|
+
# Begin column compression
|
784
|
+
# Track which columns have been compressed already to header width
|
785
|
+
header_widths = [visible_len(h) for h in header]
|
786
|
+
width_diff = [max(col_widths[i] - header_widths[i],0) for i in range(num_cols)]
|
787
|
+
total_overflow_width = table_width(col_widths, len(sep)) - cols
|
788
|
+
for i, diff in sorted(enumerate(width_diff), key=lambda x: -x[1]):
|
789
|
+
if total_overflow_width <= 0:
|
790
|
+
break
|
791
|
+
if diff <= 0:
|
792
|
+
continue
|
793
|
+
reduce_by = min(diff, total_overflow_width)
|
794
|
+
col_widths[i] -= reduce_by
|
795
|
+
total_overflow_width -= reduce_by
|
796
|
+
return render(header, rows, col_widths, sep, hsep)
|
749
797
|
|
750
798
|
def parseTable(data,sort=False):
|
751
799
|
if isinstance(data, str):
|
@@ -903,3 +951,109 @@ def print_progress_bar(iteration, total, prefix='', suffix=''):
|
|
903
951
|
except:
|
904
952
|
if iteration % 5 == 0:
|
905
953
|
print(_genrate_progress_bar(iteration, total, prefix, suffix))
|
954
|
+
|
955
|
+
def format_bytes(size, use_1024_bytes=None, to_int=False, to_str=False, str_format=".2f"):
|
956
|
+
if to_int or isinstance(size, str):
|
957
|
+
if isinstance(size, int):
|
958
|
+
return size
|
959
|
+
elif isinstance(size, str):
|
960
|
+
match = re.match(r"(\d+(\.\d+)?)\s*([a-zA-Z]*)", size)
|
961
|
+
if not match:
|
962
|
+
if to_str:
|
963
|
+
return size
|
964
|
+
print(
|
965
|
+
"Invalid size format. Expected format: 'number [unit]', "
|
966
|
+
"e.g., '1.5 GiB' or '1.5GiB'"
|
967
|
+
)
|
968
|
+
print(f"Got: {size}")
|
969
|
+
return 0
|
970
|
+
number, _, unit = match.groups()
|
971
|
+
number = float(number)
|
972
|
+
unit = unit.strip().lower().rstrip("b")
|
973
|
+
if unit.endswith("i"):
|
974
|
+
use_1024_bytes = True
|
975
|
+
elif use_1024_bytes is None:
|
976
|
+
use_1024_bytes = False
|
977
|
+
unit = unit.rstrip("i")
|
978
|
+
if use_1024_bytes:
|
979
|
+
power = 2**10
|
980
|
+
else:
|
981
|
+
power = 10**3
|
982
|
+
unit_labels = {
|
983
|
+
"": 0,
|
984
|
+
"k": 1,
|
985
|
+
"m": 2,
|
986
|
+
"g": 3,
|
987
|
+
"t": 4,
|
988
|
+
"p": 5,
|
989
|
+
"e": 6,
|
990
|
+
"z": 7,
|
991
|
+
"y": 8,
|
992
|
+
}
|
993
|
+
if unit not in unit_labels:
|
994
|
+
if to_str:
|
995
|
+
return size
|
996
|
+
else:
|
997
|
+
if to_str:
|
998
|
+
return format_bytes(
|
999
|
+
size=int(number * (power**unit_labels[unit])),
|
1000
|
+
use_1024_bytes=use_1024_bytes,
|
1001
|
+
to_str=True,
|
1002
|
+
str_format=str_format,
|
1003
|
+
)
|
1004
|
+
return int(number * (power**unit_labels[unit]))
|
1005
|
+
else:
|
1006
|
+
try:
|
1007
|
+
return int(size)
|
1008
|
+
except Exception:
|
1009
|
+
return 0
|
1010
|
+
elif to_str or isinstance(size, int) or isinstance(size, float):
|
1011
|
+
if isinstance(size, str):
|
1012
|
+
try:
|
1013
|
+
size = size.rstrip("B").rstrip("b")
|
1014
|
+
size = float(size.lower().strip())
|
1015
|
+
except Exception:
|
1016
|
+
return size
|
1017
|
+
if use_1024_bytes or use_1024_bytes is None:
|
1018
|
+
power = 2**10
|
1019
|
+
n = 0
|
1020
|
+
power_labels = {
|
1021
|
+
0: "",
|
1022
|
+
1: "Ki",
|
1023
|
+
2: "Mi",
|
1024
|
+
3: "Gi",
|
1025
|
+
4: "Ti",
|
1026
|
+
5: "Pi",
|
1027
|
+
6: "Ei",
|
1028
|
+
7: "Zi",
|
1029
|
+
8: "Yi",
|
1030
|
+
}
|
1031
|
+
while size > power:
|
1032
|
+
size /= power
|
1033
|
+
n += 1
|
1034
|
+
return f"{size:{str_format}} {' '}{power_labels[n]}".replace(" ", " ")
|
1035
|
+
else:
|
1036
|
+
power = 10**3
|
1037
|
+
n = 0
|
1038
|
+
power_labels = {
|
1039
|
+
0: "",
|
1040
|
+
1: "K",
|
1041
|
+
2: "M",
|
1042
|
+
3: "G",
|
1043
|
+
4: "T",
|
1044
|
+
5: "P",
|
1045
|
+
6: "E",
|
1046
|
+
7: "Z",
|
1047
|
+
8: "Y",
|
1048
|
+
}
|
1049
|
+
while size > power:
|
1050
|
+
size /= power
|
1051
|
+
n += 1
|
1052
|
+
return f"{size:{str_format}} {' '}{power_labels[n]}".replace(" ", " ")
|
1053
|
+
else:
|
1054
|
+
try:
|
1055
|
+
return format_bytes(float(size), use_1024_bytes)
|
1056
|
+
except Exception:
|
1057
|
+
pass
|
1058
|
+
return 0
|
1059
|
+
|
@@ -0,0 +1,6 @@
|
|
1
|
+
multiCMD.py,sha256=4niC5CAyZd9TQaxVBzftEoJUjCyQTrJa1IPm6uJbCyE,34260
|
2
|
+
multicmd-1.37.dist-info/METADATA,sha256=5lQgwCy5ZFlRQcBINnmPdGBF0StovsYOMK-HTVvdor0,5640
|
3
|
+
multicmd-1.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
+
multicmd-1.37.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
5
|
+
multicmd-1.37.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
6
|
+
multicmd-1.37.dist-info/RECORD,,
|
multicmd-1.34.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
multiCMD.py,sha256=GDefhp2vHOBFgiBmgIFa9Xls7EK95fDtuKWNW-tdopE,30274
|
2
|
-
multicmd-1.34.dist-info/METADATA,sha256=AaZBveq1NASBsdNBiUSSJFVAZ6ddb06MYM26C6t8NUY,5640
|
3
|
-
multicmd-1.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
-
multicmd-1.34.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
5
|
-
multicmd-1.34.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
6
|
-
multicmd-1.34.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|