multiSSH3 5.97__tar.gz → 5.98__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.
- {multissh3-5.97 → multissh3-5.98}/PKG-INFO +1 -1
- {multissh3-5.97 → multissh3-5.98}/README.md +0 -0
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.py +189 -97
- {multissh3-5.97 → multissh3-5.98}/setup.py +0 -0
- {multissh3-5.97 → multissh3-5.98}/test/test.py +0 -0
- {multissh3-5.97 → multissh3-5.98}/test/testCurses.py +0 -0
- {multissh3-5.97 → multissh3-5.98}/test/testCursesOld.py +0 -0
- {multissh3-5.97 → multissh3-5.98}/test/testPerfCompact.py +0 -0
- {multissh3-5.97 → multissh3-5.98}/test/testPerfExpand.py +0 -0
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.97 → multissh3-5.98}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.97 → multissh3-5.98}/setup.cfg +0 -0
|
File without changes
|
|
@@ -84,10 +84,10 @@ except Exception:
|
|
|
84
84
|
print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
|
|
85
85
|
def cache_decorator(func):
|
|
86
86
|
return func
|
|
87
|
-
version = '5.
|
|
87
|
+
version = '5.98'
|
|
88
88
|
VERSION = version
|
|
89
89
|
__version__ = version
|
|
90
|
-
COMMIT_DATE = '2025-10-
|
|
90
|
+
COMMIT_DATE = '2025-10-24'
|
|
91
91
|
|
|
92
92
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
93
93
|
'~/multiSSH3.config.json',
|
|
@@ -377,6 +377,24 @@ ERROR_MESSAGES_TO_IGNORE = [
|
|
|
377
377
|
'Killed by signal',
|
|
378
378
|
'Connection reset by peer',
|
|
379
379
|
]
|
|
380
|
+
__DEFAULT_COLOR_PALETTE = {
|
|
381
|
+
'cyan': (86, 173, 188),
|
|
382
|
+
'green': (114, 180, 43),
|
|
383
|
+
'magenta': (140, 107, 200),
|
|
384
|
+
'red': (196, 38, 94),
|
|
385
|
+
'white': (227, 227, 221),
|
|
386
|
+
'yellow': (179, 180, 43),
|
|
387
|
+
'blue': (106, 126, 200),
|
|
388
|
+
'bright_black': (102, 102, 102),
|
|
389
|
+
'bright_blue': (129, 154, 255),
|
|
390
|
+
'bright_cyan': (102, 217, 239),
|
|
391
|
+
'bright_green': (126, 226, 46),
|
|
392
|
+
'bright_magenta': (174, 129, 255),
|
|
393
|
+
'bright_red': (249, 38, 114),
|
|
394
|
+
'bright_white': (248, 248, 242),
|
|
395
|
+
'bright_yellow': (226, 226, 46),
|
|
396
|
+
}
|
|
397
|
+
COLOR_PALETTE = __DEFAULT_COLOR_PALETTE.copy()
|
|
380
398
|
_DEFAULT_CALLED = True
|
|
381
399
|
_DEFAULT_RETURN_UNFINISHED = False
|
|
382
400
|
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
|
|
@@ -936,6 +954,8 @@ def get_terminal_color_capability():
|
|
|
936
954
|
return '24bit'
|
|
937
955
|
elif "256" in term:
|
|
938
956
|
return '256'
|
|
957
|
+
elif "16" in term:
|
|
958
|
+
return '16'
|
|
939
959
|
try:
|
|
940
960
|
curses.setupterm()
|
|
941
961
|
colors = curses.tigetnum("colors")
|
|
@@ -954,96 +974,120 @@ def get_terminal_color_capability():
|
|
|
954
974
|
return 'None'
|
|
955
975
|
|
|
956
976
|
@cache_decorator
|
|
957
|
-
def
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
977
|
+
def rgb_to_ansi_color_string(r, g, b):
|
|
978
|
+
"""
|
|
979
|
+
Return an ANSI escape sequence setting the foreground to (r,g,b)
|
|
980
|
+
approximated to the terminal's capability, or '' if none.
|
|
981
|
+
"""
|
|
982
|
+
cap = get_terminal_color_capability()
|
|
983
|
+
if cap == 'None':
|
|
984
|
+
return ''
|
|
985
|
+
if cap == '24bit':
|
|
986
|
+
return f'\x1b[38;2;{r};{g};{b}m'
|
|
987
|
+
if cap == '256':
|
|
988
|
+
idx = _rgb_to_256_color(r, g, b)
|
|
989
|
+
return f'\x1b[38;5;{idx}m'
|
|
990
|
+
if cap == '16':
|
|
991
|
+
idx = _rgb_to_16_color(r, g, b)
|
|
992
|
+
# 0–7 = 30–37, 8–15 = 90–97
|
|
993
|
+
if idx < 8:
|
|
994
|
+
return f'\x1b[{30 + idx}m'
|
|
995
|
+
else:
|
|
996
|
+
return f'\x1b[{90 + (idx - 8)}m'
|
|
997
|
+
if cap == '8':
|
|
998
|
+
idx = _rgb_to_8_color(r, g, b)
|
|
999
|
+
return f'\x1b[{30 + idx}m'
|
|
1000
|
+
return ''
|
|
979
1001
|
|
|
980
|
-
|
|
981
|
-
|
|
1002
|
+
def _rgb_to_256_color(r, g, b):
|
|
1003
|
+
"""
|
|
1004
|
+
Map (r,g,b) to the 256-color cube or grayscale ramp.
|
|
982
1005
|
"""
|
|
983
|
-
|
|
984
|
-
r
|
|
985
|
-
|
|
1006
|
+
# if it’s already gray, use the 232–255 grayscale ramp
|
|
1007
|
+
if r == g == b:
|
|
1008
|
+
# 24 shades from 232 to 255
|
|
1009
|
+
return 232 + int(round(r / 255 * 23))
|
|
1010
|
+
# else map each channel to 0–5
|
|
1011
|
+
def to6(v):
|
|
1012
|
+
return int(round(v / 255 * 5))
|
|
1013
|
+
r6, g6, b6 = to6(r), to6(g), to6(b)
|
|
1014
|
+
return 16 + 36 * r6 + 6 * g6 + b6
|
|
1015
|
+
|
|
1016
|
+
def _rgb_to_16_color(r, g, b):
|
|
986
1017
|
"""
|
|
987
|
-
|
|
1018
|
+
Pick the nearest of the 16 ANSI standard colors.
|
|
1019
|
+
Returns an index 0-15.
|
|
1020
|
+
"""
|
|
1021
|
+
palette = [
|
|
1022
|
+
(0, 0, 0), # 0 black
|
|
1023
|
+
(128, 0, 0), # 1 red
|
|
1024
|
+
(0, 128, 0), # 2 green
|
|
1025
|
+
(128, 128, 0), # 3 yellow
|
|
1026
|
+
(0, 0, 128), # 4 blue
|
|
1027
|
+
(128, 0, 128), # 5 magenta
|
|
1028
|
+
(0, 128, 128), # 6 cyan
|
|
1029
|
+
(192, 192, 192), # 7 white (light gray)
|
|
1030
|
+
(128, 128, 128), # 8 bright black (dark gray)
|
|
1031
|
+
(255, 0, 0), # 9 bright red
|
|
1032
|
+
(0, 255, 0), # 10 bright green
|
|
1033
|
+
(255, 255, 0), # 11 bright yellow
|
|
1034
|
+
(0, 0, 255), # 12 bright blue
|
|
1035
|
+
(255, 0, 255), # 13 bright magenta
|
|
1036
|
+
(0, 255, 255), # 14 bright cyan
|
|
1037
|
+
(255, 255, 255), # 15 bright white
|
|
1038
|
+
]
|
|
1039
|
+
best_idx = 0
|
|
988
1040
|
best_dist = float('inf')
|
|
989
|
-
for i, (pr, pg, pb) in enumerate(
|
|
990
|
-
|
|
991
|
-
dg = pg - g
|
|
992
|
-
db = pb - b
|
|
993
|
-
dist = dr*dr + dg*dg + db*db
|
|
1041
|
+
for i, (pr, pg, pb) in enumerate(palette):
|
|
1042
|
+
dist = (r - pr)**2 + (g - pg)**2 + (b - pb)**2
|
|
994
1043
|
if dist < best_dist:
|
|
995
1044
|
best_dist = dist
|
|
996
|
-
|
|
997
|
-
return
|
|
1045
|
+
best_idx = i
|
|
1046
|
+
return best_idx
|
|
998
1047
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1048
|
+
def _rgb_to_8_color(r, g, b):
|
|
1049
|
+
"""
|
|
1050
|
+
Reduce to 8 colors by mapping to the 16-color index then clamping 0-7.
|
|
1051
|
+
"""
|
|
1052
|
+
return _rgb_to_16_color(r//2, g//2, b//2)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def int_to_color(hash_value, min_brightness=100,max_brightness=220):
|
|
1002
1056
|
r = (hash_value >> 16) & 0xFF
|
|
1003
1057
|
g = (hash_value >> 8) & 0xFF
|
|
1004
1058
|
b = hash_value & 0xFF
|
|
1005
|
-
|
|
1006
|
-
|
|
1059
|
+
brightness = math.sqrt(0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2)
|
|
1060
|
+
if brightness < min_brightness:
|
|
1061
|
+
return int_to_color(hash(str(hash_value)), min_brightness, max_brightness)
|
|
1062
|
+
if brightness > max_brightness:
|
|
1063
|
+
return int_to_color(hash(str(hash_value)), min_brightness, max_brightness)
|
|
1007
1064
|
return (r, g, b)
|
|
1008
1065
|
|
|
1009
|
-
|
|
1066
|
+
__previous_color_rgb = ()
|
|
1010
1067
|
@cache_decorator
|
|
1011
|
-
def
|
|
1068
|
+
def int_to_unique_ansi_color(number):
|
|
1012
1069
|
'''
|
|
1013
|
-
Convert a
|
|
1070
|
+
Convert a number to a unique ANSI color code
|
|
1014
1071
|
|
|
1015
1072
|
Args:
|
|
1016
|
-
|
|
1017
|
-
|
|
1073
|
+
number (int): The number to convert
|
|
1018
1074
|
Returns:
|
|
1019
1075
|
int: The ANSI color code
|
|
1020
1076
|
'''
|
|
1021
|
-
global
|
|
1077
|
+
global __previous_color_rgb
|
|
1022
1078
|
# Use a hash function to generate a consistent integer from the string
|
|
1023
1079
|
color_capability = get_terminal_color_capability()
|
|
1024
|
-
index = None
|
|
1025
1080
|
if color_capability == 'None':
|
|
1026
1081
|
return ''
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
index = (hash(string) % 14) + 31
|
|
1030
|
-
if index > 37:
|
|
1031
|
-
index += 52 # Bright colors (90-97)
|
|
1032
|
-
elif color_capability == '8':
|
|
1033
|
-
index = (hash(string) % 6) + 31
|
|
1034
|
-
r,g,b = hashable_to_color(string)
|
|
1035
|
-
if color_capability == '256':
|
|
1036
|
-
index = rgb_to_xterm_index(r,g,b)
|
|
1037
|
-
if index:
|
|
1038
|
-
if index == __previous_ansi_color_index:
|
|
1039
|
-
return string_to_unique_ansi_color(hash(string))
|
|
1040
|
-
__previous_ansi_color_index = index
|
|
1041
|
-
if color_capability == '256':
|
|
1042
|
-
return f'\033[38;5;{index}m'
|
|
1043
|
-
else:
|
|
1044
|
-
return f'\033[{index}m'
|
|
1082
|
+
if color_capability == '24bit':
|
|
1083
|
+
r, g, b = int_to_color(number)
|
|
1045
1084
|
else:
|
|
1046
|
-
|
|
1085
|
+
# for 256 colors and below, reduce brightness threshold as we do not have many color to work with
|
|
1086
|
+
r, g, b = int_to_color(number, min_brightness=70, max_brightness=190)
|
|
1087
|
+
if sum(abs(a - b) for a, b in zip((r, g, b), __previous_color_rgb)) <= 256:
|
|
1088
|
+
r, g, b = int_to_color(hash(str(number)))
|
|
1089
|
+
__previous_color_rgb = (r, g, b)
|
|
1090
|
+
return rgb_to_ansi_color_string(r, g, b)
|
|
1047
1091
|
|
|
1048
1092
|
#%% ------------ Compacting Hostnames ----------------
|
|
1049
1093
|
def __tokenize_hostname(hostname):
|
|
@@ -2795,7 +2839,7 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2795
2839
|
indexes = Counter({hostname: 0 for hostname in merging_hostnames})
|
|
2796
2840
|
working_index_keys = set(merging_hostnames)
|
|
2797
2841
|
previousBuddies = set()
|
|
2798
|
-
hostnameWrapper = textwrap.TextWrapper(width=line_length -
|
|
2842
|
+
hostnameWrapper = textwrap.TextWrapper(width=line_length -1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent='─ ', subsequent_indent='- ')
|
|
2799
2843
|
hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
|
|
2800
2844
|
diff_display_item_count = max(1,int(max(map(len, outputs_by_hostname.values())) * (1 - diff_display_threshold)))
|
|
2801
2845
|
def get_multiset_index_for_hostname(hostname):
|
|
@@ -2867,12 +2911,14 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2867
2911
|
if buddy != previousBuddies:
|
|
2868
2912
|
hostnameStr = ','.join(compact_hostnames(buddy))
|
|
2869
2913
|
hostnameLines = hostnameWrapper.wrap(hostnameStr)
|
|
2870
|
-
hostnameLines = [line.ljust(line_length
|
|
2871
|
-
color =
|
|
2872
|
-
|
|
2914
|
+
hostnameLines = [line.ljust(line_length) for line in hostnameLines]
|
|
2915
|
+
color = int_to_unique_ansi_color(hash(hostnameStr)) if len(buddy) < len(merging_hostnames) else ''
|
|
2916
|
+
if color:
|
|
2917
|
+
color = f"\033[0m{color}"
|
|
2918
|
+
hostnameLines[0] = f"{color}{hostnameLines[0]}"
|
|
2873
2919
|
output.extend(hostnameLines)
|
|
2874
2920
|
previousBuddies = buddy
|
|
2875
|
-
output.append(lineToAdd.ljust(line_length
|
|
2921
|
+
output.append(lineToAdd.ljust(line_length))
|
|
2876
2922
|
currentLines[lineToAdd].difference_update(buddy)
|
|
2877
2923
|
if not currentLines[lineToAdd]:
|
|
2878
2924
|
del currentLines[lineToAdd]
|
|
@@ -2896,17 +2942,24 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2896
2942
|
|
|
2897
2943
|
def mergeOutputs(outputs_by_hostname, merge_groups, remaining_hostnames, diff_display_threshold, line_length):
|
|
2898
2944
|
output = []
|
|
2899
|
-
|
|
2900
|
-
|
|
2945
|
+
color_cap = get_terminal_color_capability()
|
|
2946
|
+
if color_cap == 'None':
|
|
2947
|
+
color_line = ''
|
|
2948
|
+
color_reset = ''
|
|
2949
|
+
else:
|
|
2950
|
+
color_line = rgb_to_ansi_color_string(*COLOR_PALETTE.get('white', __DEFAULT_COLOR_PALETTE['white']))
|
|
2951
|
+
color_reset = '\033[0m'
|
|
2952
|
+
output.append(color_line+'─'*(line_length)+color_reset)
|
|
2953
|
+
hostnameWrapper = textwrap.TextWrapper(width=line_length - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent='─ ', subsequent_indent='- ')
|
|
2901
2954
|
hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
|
|
2902
2955
|
for merging_hostnames in merge_groups:
|
|
2903
2956
|
mergeOutput(merging_hostnames, outputs_by_hostname, output, diff_display_threshold,line_length)
|
|
2904
|
-
output.append(
|
|
2957
|
+
output.append(color_line+'─'*(line_length)+color_reset)
|
|
2905
2958
|
for hostname in remaining_hostnames:
|
|
2906
2959
|
hostnameLines = hostnameWrapper.wrap(','.join(compact_hostnames([hostname])))
|
|
2907
|
-
output.extend(line.ljust(line_length
|
|
2908
|
-
output.extend(line.ljust(line_length
|
|
2909
|
-
output.append(
|
|
2960
|
+
output.extend(line.ljust(line_length ) for line in hostnameLines)
|
|
2961
|
+
output.extend(line.ljust(line_length ) for line in outputs_by_hostname[hostname])
|
|
2962
|
+
output.append(color_line+'─'*(line_length)+color_reset)
|
|
2910
2963
|
if output:
|
|
2911
2964
|
output.pop()
|
|
2912
2965
|
# if output and output[0] and output[0].startswith('├'):
|
|
@@ -2931,26 +2984,40 @@ def get_host_raw_output(hosts, terminal_width):
|
|
|
2931
2984
|
outputs_by_hostname = {}
|
|
2932
2985
|
line_bag_by_hostname = {}
|
|
2933
2986
|
hostnames_by_line_bag_len = {}
|
|
2934
|
-
text_wrapper = textwrap.TextWrapper(width=terminal_width -
|
|
2935
|
-
initial_indent='
|
|
2987
|
+
text_wrapper = textwrap.TextWrapper(width=terminal_width - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
2988
|
+
initial_indent=' ', subsequent_indent='-')
|
|
2936
2989
|
max_length = 20
|
|
2990
|
+
color_cap = get_terminal_color_capability()
|
|
2991
|
+
if color_cap == 'None':
|
|
2992
|
+
color_reset_str = ''
|
|
2993
|
+
blue_str = ''
|
|
2994
|
+
cyan_str = ''
|
|
2995
|
+
green_str = ''
|
|
2996
|
+
red_str = ''
|
|
2997
|
+
else:
|
|
2998
|
+
color_reset_str = '\033[0m'
|
|
2999
|
+
blue_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('blue', __DEFAULT_COLOR_PALETTE['blue']))
|
|
3000
|
+
cyan_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_cyan', __DEFAULT_COLOR_PALETTE['bright_cyan']))
|
|
3001
|
+
green_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_green', __DEFAULT_COLOR_PALETTE['bright_green']))
|
|
3002
|
+
red_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_red', __DEFAULT_COLOR_PALETTE['bright_red']))
|
|
2937
3003
|
hosts = pre_merge_hosts(hosts)
|
|
2938
3004
|
for host in hosts:
|
|
2939
3005
|
max_length = max(max_length, len(max(host.name.split(','), key=len)) + 3)
|
|
2940
|
-
hostPrintOut = ["
|
|
3006
|
+
hostPrintOut = [f"{cyan_str}█{color_reset_str} EXECUTED COMMAND:"]
|
|
2941
3007
|
for line in host.command.splitlines():
|
|
2942
3008
|
hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2943
3009
|
# hostPrintOut.extend(itertools.chain.from_iterable(text_wrapper.wrap(line) for line in host['command'].splitlines()))
|
|
2944
3010
|
lineBag = {(0,host.command)}
|
|
2945
3011
|
prevLine = host.command
|
|
2946
3012
|
if host.stdout:
|
|
2947
|
-
hostPrintOut.append('
|
|
3013
|
+
hostPrintOut.append(f'{blue_str}▓{color_reset_str} STDOUT:')
|
|
2948
3014
|
# for line in host.stdout:
|
|
2949
3015
|
# if len(line) < terminal_width - 2:
|
|
2950
|
-
# hostPrintOut.append(f"
|
|
3016
|
+
# hostPrintOut.append(f" {line}")
|
|
2951
3017
|
# else:
|
|
2952
3018
|
# hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2953
|
-
hostPrintOut.extend(f"
|
|
3019
|
+
hostPrintOut.extend(f" {line}" for line in host.stdout)
|
|
3020
|
+
max_length = max(max_length, max(map(len, host.stdout)))
|
|
2954
3021
|
# hostPrintOut.extend(text_wrapper.wrap(line) for line in host.stdout)
|
|
2955
3022
|
lineBag.add((prevLine,1))
|
|
2956
3023
|
lineBag.add((1,host.stdout[0]))
|
|
@@ -2966,22 +3033,26 @@ def get_host_raw_output(hosts, terminal_width):
|
|
|
2966
3033
|
elif host.stderr[-1].strip().endswith('No route to host'):
|
|
2967
3034
|
host.stderr[-1] = 'Cannot find host!'
|
|
2968
3035
|
if host.stderr:
|
|
2969
|
-
hostPrintOut.append('
|
|
3036
|
+
hostPrintOut.append(f'{red_str}▒{color_reset_str} STDERR:')
|
|
2970
3037
|
# for line in host.stderr:
|
|
2971
3038
|
# if len(line) < terminal_width - 2:
|
|
2972
|
-
# hostPrintOut.append(f"
|
|
3039
|
+
# hostPrintOut.append(f" {line}")
|
|
2973
3040
|
# else:
|
|
2974
3041
|
# hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2975
|
-
hostPrintOut.extend(f"
|
|
3042
|
+
hostPrintOut.extend(f" {line}" for line in host.stderr)
|
|
3043
|
+
max_length = max(max_length, max(map(len, host.stderr)))
|
|
2976
3044
|
lineBag.add((prevLine,2))
|
|
2977
3045
|
lineBag.add((2,host.stderr[0]))
|
|
2978
3046
|
lineBag.update(host.stderr)
|
|
2979
3047
|
if len(host.stderr) > 1:
|
|
2980
3048
|
lineBag.update(zip(host.stderr, host.stderr[1:]))
|
|
2981
3049
|
prevLine = host.stderr[-1]
|
|
2982
|
-
|
|
3050
|
+
if host.returncode != 0:
|
|
3051
|
+
codeColor = red_str
|
|
3052
|
+
else:
|
|
3053
|
+
codeColor = green_str
|
|
3054
|
+
hostPrintOut.append(f"{codeColor}░{color_reset_str} RETURN CODE: {host.returncode}")
|
|
2983
3055
|
lineBag.add((prevLine,f"{host.returncode}"))
|
|
2984
|
-
max_length = max(max_length, max(map(len, hostPrintOut)))
|
|
2985
3056
|
outputs_by_hostname[host.name] = hostPrintOut
|
|
2986
3057
|
line_bag_by_hostname[host.name] = lineBag
|
|
2987
3058
|
hostnames_by_line_bag_len.setdefault(len(lineBag), set()).add(host.name)
|
|
@@ -3022,6 +3093,7 @@ def form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_le
|
|
|
3022
3093
|
return merge_groups, remaining_hostnames
|
|
3023
3094
|
|
|
3024
3095
|
def generate_output(hosts, usejson = False, greppable = False,quiet = False,encoding = _encoding,keyPressesIn = [[]]):
|
|
3096
|
+
color_cap = get_terminal_color_capability()
|
|
3025
3097
|
if quiet:
|
|
3026
3098
|
# remove hosts with returncode 0
|
|
3027
3099
|
hosts = [host for host in hosts if host.returncode != 0]
|
|
@@ -3029,7 +3101,10 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
|
|
|
3029
3101
|
if usejson:
|
|
3030
3102
|
return '{"Success": true}'
|
|
3031
3103
|
else:
|
|
3032
|
-
|
|
3104
|
+
if color_cap == 'None':
|
|
3105
|
+
return 'Success'
|
|
3106
|
+
else:
|
|
3107
|
+
return '\033[32mSuccess\033[0m'
|
|
3033
3108
|
if usejson:
|
|
3034
3109
|
# [print(dict(host)) for host in hosts]
|
|
3035
3110
|
#print(json.dumps([dict(host) for host in hosts],indent=4))
|
|
@@ -3064,23 +3139,32 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
|
|
|
3064
3139
|
except Exception:
|
|
3065
3140
|
eprint("Warning: diff_display_threshold should be a float between 0 and 1. Setting to default value of 0.9")
|
|
3066
3141
|
diff_display_threshold = 0.9
|
|
3142
|
+
|
|
3143
|
+
color_reset_str = '' if color_cap == 'None' else '\033[0m'
|
|
3144
|
+
white_str = '' if color_cap == 'None' else rgb_to_ansi_color_string(*COLOR_PALETTE.get('white', __DEFAULT_COLOR_PALETTE['white']))
|
|
3067
3145
|
terminal_length = get_terminal_size()[0]
|
|
3068
3146
|
outputs_by_hostname, line_bag_by_hostname, hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_length = get_host_raw_output(hosts,terminal_length)
|
|
3069
3147
|
merge_groups ,remaining_hostnames = form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_bag_by_hostname, diff_display_threshold)
|
|
3070
3148
|
outputs = mergeOutputs(outputs_by_hostname, merge_groups,remaining_hostnames, diff_display_threshold,line_length)
|
|
3071
3149
|
if keyPressesIn[-1]:
|
|
3072
3150
|
CMDsOut = [''.join(cmd).encode(encoding=encoding,errors='backslashreplace').decode(encoding=encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in keyPressesIn if cmd]
|
|
3073
|
-
outputs.append("
|
|
3151
|
+
outputs.append(color_reset_str + "─ User Inputs:".ljust(line_length,'─'))
|
|
3074
3152
|
cmdOut = []
|
|
3075
3153
|
for line in CMDsOut:
|
|
3076
3154
|
cmdOut.extend(textwrap.wrap(line, width=line_length-1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
3077
|
-
initial_indent='
|
|
3078
|
-
outputs.extend(cmd.ljust(line_length
|
|
3155
|
+
initial_indent=' ', subsequent_indent='-'))
|
|
3156
|
+
outputs.extend(cmd.ljust(line_length) for cmd in cmdOut)
|
|
3079
3157
|
keyPressesIn[-1].clear()
|
|
3080
3158
|
if not outputs:
|
|
3081
|
-
|
|
3159
|
+
if quiet:
|
|
3160
|
+
if color_cap == 'None':
|
|
3161
|
+
return 'Success'
|
|
3162
|
+
else:
|
|
3163
|
+
return '\033[32mSuccess\033[0m'
|
|
3164
|
+
else:
|
|
3165
|
+
rtnStr = ''
|
|
3082
3166
|
else:
|
|
3083
|
-
rtnStr = '\n'.join(outputs + [
|
|
3167
|
+
rtnStr = '\n'.join(outputs + [white_str + '─' * (line_length) + color_reset_str])
|
|
3084
3168
|
return rtnStr
|
|
3085
3169
|
|
|
3086
3170
|
def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
@@ -3126,7 +3210,15 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
3126
3210
|
if total_sleeped > 0.1:
|
|
3127
3211
|
break
|
|
3128
3212
|
if any([host.returncode is None for host in hosts]):
|
|
3129
|
-
|
|
3213
|
+
try:
|
|
3214
|
+
curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
|
|
3215
|
+
except Exception:
|
|
3216
|
+
try:
|
|
3217
|
+
curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
|
|
3218
|
+
except Exception as e:
|
|
3219
|
+
eprint(f"Curses print error: {e}")
|
|
3220
|
+
import traceback
|
|
3221
|
+
print(traceback.format_exc())
|
|
3130
3222
|
if not returnUnfinished:
|
|
3131
3223
|
# wait until all hosts have a return code
|
|
3132
3224
|
while any([host.returncode is None for host in hosts]):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|