multiCMD 1.34__py3-none-any.whl → 1.35__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 +197 -45
- {multicmd-1.34.dist-info → multicmd-1.35.dist-info}/METADATA +1 -1
- multicmd-1.35.dist-info/RECORD +6 -0
- multicmd-1.34.dist-info/RECORD +0 -6
- {multicmd-1.34.dist-info → multicmd-1.35.dist-info}/WHEEL +0 -0
- {multicmd-1.34.dist-info → multicmd-1.35.dist-info}/entry_points.txt +0 -0
- {multicmd-1.34.dist-info → multicmd-1.35.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.35'
|
24
24
|
__version__ = version
|
25
|
-
COMMIT_DATE = '2025-
|
25
|
+
COMMIT_DATE = '2025-09-10'
|
26
26
|
__running_threads = set()
|
27
27
|
__variables = {}
|
28
28
|
|
@@ -679,73 +679,119 @@ def input_with_timeout_and_countdown(timeout, prompt='Please enter your selectio
|
|
679
679
|
# If there is no input, return None
|
680
680
|
return None
|
681
681
|
|
682
|
-
def pretty_format_table(data, delimiter
|
683
|
-
|
682
|
+
def pretty_format_table(data, delimiter="\t", header=None, full=False):
|
683
|
+
import re
|
684
|
+
version = 1.12
|
684
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)] + ".."
|
685
701
|
if not data:
|
686
|
-
return
|
702
|
+
return ""
|
703
|
+
# Normalize input data structure
|
687
704
|
if isinstance(data, str):
|
688
|
-
data = data.strip(
|
705
|
+
data = data.strip("\n").split("\n")
|
689
706
|
data = [line.split(delimiter) for line in data]
|
690
707
|
elif isinstance(data, dict):
|
691
|
-
# flatten the 2D dict to a list of lists
|
692
708
|
if isinstance(next(iter(data.values())), dict):
|
693
|
-
tempData = [[
|
694
|
-
tempData.extend(
|
709
|
+
tempData = [["key"] + list(next(iter(data.values())).keys())]
|
710
|
+
tempData.extend([[key] + list(value.values()) for key, value in data.items()])
|
695
711
|
data = tempData
|
696
712
|
else:
|
697
|
-
# it is a dict of lists
|
698
713
|
data = [[key] + list(value) for key, value in data.items()]
|
699
714
|
elif not isinstance(data, list):
|
700
715
|
data = list(data)
|
701
|
-
# format the list into 2d list of list of strings
|
702
716
|
if isinstance(data[0], dict):
|
703
|
-
tempData = [data[0].keys()]
|
717
|
+
tempData = [list(data[0].keys())]
|
704
718
|
tempData.extend([list(item.values()) for item in data])
|
705
719
|
data = tempData
|
706
720
|
data = [[str(item) for item in row] for row in data]
|
707
721
|
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:
|
722
|
+
# Resolve header and rows
|
723
|
+
using_provided_header = header is not None
|
724
|
+
if not using_provided_header:
|
721
725
|
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))
|
726
|
+
rows = data[1:]
|
731
727
|
else:
|
732
|
-
|
733
|
-
if isinstance(header,str):
|
728
|
+
if isinstance(header, str):
|
734
729
|
header = header.split(delimiter)
|
730
|
+
# Pad/trim header to match num_cols
|
735
731
|
if len(header) < num_cols:
|
736
|
-
header
|
732
|
+
header = header + [""] * (num_cols - len(header))
|
737
733
|
elif len(header) > num_cols:
|
738
734
|
header = header[:num_cols]
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
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:
|
744
765
|
if not any(row):
|
745
|
-
|
766
|
+
out.append(hsep_str.join("-" * w for w in col_w))
|
746
767
|
else:
|
747
|
-
|
748
|
-
|
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)
|
749
795
|
|
750
796
|
def parseTable(data,sort=False):
|
751
797
|
if isinstance(data, str):
|
@@ -903,3 +949,109 @@ def print_progress_bar(iteration, total, prefix='', suffix=''):
|
|
903
949
|
except:
|
904
950
|
if iteration % 5 == 0:
|
905
951
|
print(_genrate_progress_bar(iteration, total, prefix, suffix))
|
952
|
+
|
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
|
1057
|
+
|
@@ -0,0 +1,6 @@
|
|
1
|
+
multiCMD.py,sha256=X7a0Bl0bt94WjcvhsPU9Rkw27xsi5wqD9eXZnUMCBi0,34221
|
2
|
+
multicmd-1.35.dist-info/METADATA,sha256=qCzCX1_GssjELHkOC0JXZ0S9e8yORJ9-6DvDocXYaTY,5640
|
3
|
+
multicmd-1.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
+
multicmd-1.35.dist-info/entry_points.txt,sha256=nSLBkYrcUCQxt1w3LIJkvgOhpRYEC0xAPqNG7u4OYs8,89
|
5
|
+
multicmd-1.35.dist-info/top_level.txt,sha256=DSqgftD40G09F3qEjpHRCUNUsGUvGZZG69Sm3YEUiWI,9
|
6
|
+
multicmd-1.35.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
|