multiSSH3 5.90__tar.gz → 5.91__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.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- {multissh3-5.90 → multissh3-5.91}/PKG-INFO +1 -1
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.py +183 -25
- {multissh3-5.90 → multissh3-5.91}/README.md +0 -0
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.90 → multissh3-5.91}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.90 → multissh3-5.91}/setup.cfg +0 -0
- {multissh3-5.90 → multissh3-5.91}/setup.py +0 -0
- {multissh3-5.90 → multissh3-5.91}/test/test.py +0 -0
- {multissh3-5.90 → multissh3-5.91}/test/testCurses.py +0 -0
- {multissh3-5.90 → multissh3-5.91}/test/testCursesOld.py +0 -0
- {multissh3-5.90 → multissh3-5.91}/test/testPerfCompact.py +0 -0
- {multissh3-5.90 → multissh3-5.91}/test/testPerfExpand.py +0 -0
|
@@ -24,6 +24,7 @@ import string
|
|
|
24
24
|
import subprocess
|
|
25
25
|
import sys
|
|
26
26
|
import tempfile
|
|
27
|
+
import textwrap
|
|
27
28
|
import threading
|
|
28
29
|
import time
|
|
29
30
|
import typing
|
|
@@ -83,7 +84,7 @@ except Exception:
|
|
|
83
84
|
print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
|
|
84
85
|
def cache_decorator(func):
|
|
85
86
|
return func
|
|
86
|
-
version = '5.
|
|
87
|
+
version = '5.91'
|
|
87
88
|
VERSION = version
|
|
88
89
|
__version__ = version
|
|
89
90
|
COMMIT_DATE = '2025-10-17'
|
|
@@ -376,6 +377,7 @@ DEFAULT_SKIP_HOSTS = ''
|
|
|
376
377
|
DEFAULT_ENCODING = 'utf-8'
|
|
377
378
|
DEFAULT_DIFF_DISPLAY_THRESHOLD = 0.75
|
|
378
379
|
SSH_STRICT_HOST_KEY_CHECKING = False
|
|
380
|
+
FORCE_TRUECOLOR = False
|
|
379
381
|
ERROR_MESSAGES_TO_IGNORE = [
|
|
380
382
|
'Pseudo-terminal will not be allocated because stdin is not a terminal',
|
|
381
383
|
'Connection to .* closed',
|
|
@@ -792,6 +794,135 @@ def get_terminal_size():
|
|
|
792
794
|
import shutil
|
|
793
795
|
_tsize = shutil.get_terminal_size(fallback=(120, 30))
|
|
794
796
|
return _tsize
|
|
797
|
+
|
|
798
|
+
@cache_decorator
|
|
799
|
+
def get_terminal_color_capability():
|
|
800
|
+
global FORCE_TRUECOLOR
|
|
801
|
+
if not sys.stdout.isatty():
|
|
802
|
+
return 'None'
|
|
803
|
+
term = os.environ.get("TERM", "")
|
|
804
|
+
if term == "dumb":
|
|
805
|
+
return 'None'
|
|
806
|
+
elif term == "linux":
|
|
807
|
+
return '8'
|
|
808
|
+
elif FORCE_TRUECOLOR:
|
|
809
|
+
return '24bit'
|
|
810
|
+
colorterm = os.environ.get("COLORTERM", "")
|
|
811
|
+
if colorterm in ("truecolor", "24bit", "24-bit"):
|
|
812
|
+
return '24bit'
|
|
813
|
+
if term in ("xterm-truecolor", "xterm-24bit", "xterm-kitty", "alacritty", "wezterm", "foot", "terminology"):
|
|
814
|
+
return '24bit'
|
|
815
|
+
elif "256" in term:
|
|
816
|
+
return '256'
|
|
817
|
+
try:
|
|
818
|
+
curses.setupterm()
|
|
819
|
+
colors = curses.tigetnum("colors")
|
|
820
|
+
# tigetnum returns -1 if the capability isn’t defined
|
|
821
|
+
if colors >= 16777216:
|
|
822
|
+
return '24bit'
|
|
823
|
+
elif colors >= 256:
|
|
824
|
+
return '256'
|
|
825
|
+
elif colors >= 16:
|
|
826
|
+
return '16'
|
|
827
|
+
elif colors > 0:
|
|
828
|
+
return '8'
|
|
829
|
+
else:
|
|
830
|
+
return 'None'
|
|
831
|
+
except Exception:
|
|
832
|
+
return 'None'
|
|
833
|
+
|
|
834
|
+
@cache_decorator
|
|
835
|
+
def get_xterm256_palette():
|
|
836
|
+
palette = []
|
|
837
|
+
# 0–15: system colors (we'll just fill with dummy values;
|
|
838
|
+
# you could fill in real RGB if you need to)
|
|
839
|
+
system_colors = [
|
|
840
|
+
(0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0),
|
|
841
|
+
(0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192),
|
|
842
|
+
(128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0),
|
|
843
|
+
(0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255),
|
|
844
|
+
]
|
|
845
|
+
palette.extend(system_colors)
|
|
846
|
+
# 16–231: 6x6x6 color cube
|
|
847
|
+
levels = [0, 95, 135, 175, 215, 255]
|
|
848
|
+
for r in levels:
|
|
849
|
+
for g in levels:
|
|
850
|
+
for b in levels:
|
|
851
|
+
palette.append((r, g, b))
|
|
852
|
+
# 232–255: grayscale ramp, 24 steps from 8 to 238
|
|
853
|
+
for i in range(24):
|
|
854
|
+
level = 8 + i * 10
|
|
855
|
+
palette.append((level, level, level))
|
|
856
|
+
return palette
|
|
857
|
+
|
|
858
|
+
@cache_decorator
|
|
859
|
+
def rgb_to_xterm_index(r, g, b):
|
|
860
|
+
"""
|
|
861
|
+
Map 24-bit RGB to nearest xterm-256 color index.
|
|
862
|
+
r, g, b should be in 0-255.
|
|
863
|
+
Returns an int in 0-255.
|
|
864
|
+
"""
|
|
865
|
+
best_index = 0
|
|
866
|
+
best_dist = float('inf')
|
|
867
|
+
for i, (pr, pg, pb) in enumerate(get_xterm256_palette()):
|
|
868
|
+
dr = pr - r
|
|
869
|
+
dg = pg - g
|
|
870
|
+
db = pb - b
|
|
871
|
+
dist = dr*dr + dg*dg + db*db
|
|
872
|
+
if dist < best_dist:
|
|
873
|
+
best_dist = dist
|
|
874
|
+
best_index = i
|
|
875
|
+
return best_index
|
|
876
|
+
|
|
877
|
+
@cache_decorator
|
|
878
|
+
def hashable_to_color(n, brightness_threshold=500):
|
|
879
|
+
hash_value = hash(str(n))
|
|
880
|
+
r = (hash_value >> 16) & 0xFF
|
|
881
|
+
g = (hash_value >> 8) & 0xFF
|
|
882
|
+
b = hash_value & 0xFF
|
|
883
|
+
if (r + g + b) < brightness_threshold:
|
|
884
|
+
return hashable_to_color(hash_value, brightness_threshold)
|
|
885
|
+
return (r, g, b)
|
|
886
|
+
|
|
887
|
+
__previous_ansi_color_index = -1
|
|
888
|
+
@cache_decorator
|
|
889
|
+
def string_to_unique_ansi_color(string):
|
|
890
|
+
'''
|
|
891
|
+
Convert a string to a unique ANSI color code
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
string (str): The string to convert
|
|
895
|
+
|
|
896
|
+
Returns:
|
|
897
|
+
int: The ANSI color code
|
|
898
|
+
'''
|
|
899
|
+
global __previous_ansi_color_index
|
|
900
|
+
# Use a hash function to generate a consistent integer from the string
|
|
901
|
+
color_capability = get_terminal_color_capability()
|
|
902
|
+
index = None
|
|
903
|
+
if color_capability == 'None':
|
|
904
|
+
return ''
|
|
905
|
+
elif color_capability == '16':
|
|
906
|
+
# Map to one of the 14 colors (31-37, 90-96), avoiding black and white
|
|
907
|
+
index = (hash(string) % 14) + 31
|
|
908
|
+
if index > 37:
|
|
909
|
+
index += 52 # Bright colors (90-97)
|
|
910
|
+
elif color_capability == '8':
|
|
911
|
+
index = (hash(string) % 6) + 31
|
|
912
|
+
r,g,b = hashable_to_color(string)
|
|
913
|
+
if color_capability == '256':
|
|
914
|
+
index = rgb_to_xterm_index(r,g,b)
|
|
915
|
+
if index:
|
|
916
|
+
if index == __previous_ansi_color_index:
|
|
917
|
+
return string_to_unique_ansi_color(hash(string))
|
|
918
|
+
__previous_ansi_color_index = index
|
|
919
|
+
if color_capability == '256':
|
|
920
|
+
return f'\033[38;5;{index}m'
|
|
921
|
+
else:
|
|
922
|
+
return f'\033[{index}m'
|
|
923
|
+
else:
|
|
924
|
+
return f'\033[38;2;{r};{g};{b}m'
|
|
925
|
+
|
|
795
926
|
#%% ------------ Compacting Hostnames ----------------
|
|
796
927
|
def __tokenize_hostname(hostname):
|
|
797
928
|
"""
|
|
@@ -2538,12 +2669,12 @@ def can_merge(line_bag1, line_bag2, threshold):
|
|
|
2538
2669
|
return False
|
|
2539
2670
|
return len(line_bag1.symmetric_difference(line_bag2)) < max((len(line_bag1) + len(line_bag2)) * (1 - threshold),1)
|
|
2540
2671
|
|
|
2541
|
-
def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_threshold):
|
|
2542
|
-
terminal_length = get_terminal_size()[0]
|
|
2543
|
-
output.append(('├'+'─'*(terminal_length-1)))
|
|
2672
|
+
def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_threshold,line_length):
|
|
2544
2673
|
indexes = {hostname: 0 for hostname in merging_hostnames}
|
|
2545
2674
|
working_indexes = indexes.copy()
|
|
2546
2675
|
previousBuddies = set()
|
|
2676
|
+
hostnameWrapper = textwrap.TextWrapper(width=line_length - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent='├─ ', subsequent_indent='│- ')
|
|
2677
|
+
hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
|
|
2547
2678
|
while indexes:
|
|
2548
2679
|
futures = {}
|
|
2549
2680
|
defer = False
|
|
@@ -2566,9 +2697,14 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2566
2697
|
break
|
|
2567
2698
|
if not defer:
|
|
2568
2699
|
if buddy != previousBuddies:
|
|
2569
|
-
|
|
2700
|
+
hostnameStr = ','.join(compact_hostnames(buddy))
|
|
2701
|
+
hostnameLines = hostnameWrapper.wrap(hostnameStr)
|
|
2702
|
+
hostnameLines = [line.ljust(line_length - 1) + '│' for line in hostnameLines]
|
|
2703
|
+
color = string_to_unique_ansi_color(hostnameStr) if len(buddy) < len(merging_hostnames) else ''
|
|
2704
|
+
hostnameLines[0] = f"\033[0m{color}{hostnameLines[0]}"
|
|
2705
|
+
output.extend(hostnameLines)
|
|
2570
2706
|
previousBuddies = buddy
|
|
2571
|
-
output.append(lineToAdd)
|
|
2707
|
+
output.append(lineToAdd.ljust(line_length - 1) + '│')
|
|
2572
2708
|
for hostname in buddy:
|
|
2573
2709
|
indexes[hostname] += 1
|
|
2574
2710
|
if indexes[hostname] >= len(outputs_by_hostname[hostname]):
|
|
@@ -2587,29 +2723,41 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2587
2723
|
futures[hostname] = (tracking_multiset, tracking_index)
|
|
2588
2724
|
working_indexes = indexes.copy()
|
|
2589
2725
|
|
|
2590
|
-
def mergeOutputs(outputs_by_hostname, merge_groups, remaining_hostnames, diff_display_threshold):
|
|
2591
|
-
terminal_length = get_terminal_size()[0]
|
|
2726
|
+
def mergeOutputs(outputs_by_hostname, merge_groups, remaining_hostnames, diff_display_threshold, line_length):
|
|
2592
2727
|
output = []
|
|
2728
|
+
output.append(('┌'+'─'*(line_length-2) + '┐'))
|
|
2593
2729
|
for merging_hostnames in merge_groups:
|
|
2594
|
-
mergeOutput(merging_hostnames, outputs_by_hostname, output, diff_display_threshold)
|
|
2730
|
+
mergeOutput(merging_hostnames, outputs_by_hostname, output, diff_display_threshold,line_length)
|
|
2731
|
+
output.append('\033[0m├'+'─'*(line_length-2) + '┤')
|
|
2595
2732
|
for hostname in remaining_hostnames:
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
output.extend(
|
|
2733
|
+
hostnameLines = textwrap.wrap(hostname, width=line_length-1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
2734
|
+
initial_indent='├─ ', subsequent_indent='│- ')
|
|
2735
|
+
output.extend(line.ljust(line_length - 1) + '│' for line in hostnameLines)
|
|
2736
|
+
output.extend(line.ljust(line_length - 1) + '│' for line in outputs_by_hostname[hostname])
|
|
2737
|
+
output.append('\033[0m├'+'─'*(line_length-2) + '┤')
|
|
2738
|
+
if output:
|
|
2739
|
+
output.pop()
|
|
2740
|
+
# if output and output[0] and output[0].startswith('├'):
|
|
2741
|
+
# output[0] = '┌' + output[0][1:]
|
|
2599
2742
|
return output
|
|
2600
2743
|
|
|
2601
|
-
def get_host_raw_output(hosts):
|
|
2744
|
+
def get_host_raw_output(hosts, terminal_width):
|
|
2602
2745
|
outputs_by_hostname = {}
|
|
2603
2746
|
line_bag_by_hostname = {}
|
|
2604
2747
|
hostnames_by_line_bag_len = {}
|
|
2748
|
+
text_wrapper = textwrap.TextWrapper(width=terminal_width - 2, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
2749
|
+
initial_indent='│ ', subsequent_indent='│-')
|
|
2750
|
+
max_length = 20
|
|
2605
2751
|
for host in hosts:
|
|
2606
|
-
hostPrintOut = ["│█ EXECUTED COMMAND"]
|
|
2607
|
-
|
|
2752
|
+
hostPrintOut = ["│█ EXECUTED COMMAND:"]
|
|
2753
|
+
for line in host['command'].splitlines():
|
|
2754
|
+
hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2608
2755
|
lineBag = {(0,host['command'])}
|
|
2609
2756
|
prevLine = host['command']
|
|
2610
2757
|
if host['stdout']:
|
|
2611
2758
|
hostPrintOut.append('│▓ STDOUT:')
|
|
2612
|
-
|
|
2759
|
+
for line in host['stdout']:
|
|
2760
|
+
hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2613
2761
|
lineBag.add((prevLine,1))
|
|
2614
2762
|
lineBag.add((1,host['stdout'][0]))
|
|
2615
2763
|
if len(host['stdout']) > 1:
|
|
@@ -2625,7 +2773,8 @@ def get_host_raw_output(hosts):
|
|
|
2625
2773
|
host['stderr'][-1] = 'Cannot find host!'
|
|
2626
2774
|
if host['stderr']:
|
|
2627
2775
|
hostPrintOut.append('│▒ STDERR:')
|
|
2628
|
-
|
|
2776
|
+
for line in host['stderr']:
|
|
2777
|
+
hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2629
2778
|
lineBag.add((prevLine,2))
|
|
2630
2779
|
lineBag.add((2,host['stderr'][0]))
|
|
2631
2780
|
lineBag.update(host['stderr'])
|
|
@@ -2634,10 +2783,11 @@ def get_host_raw_output(hosts):
|
|
|
2634
2783
|
prevLine = host['stderr'][-1]
|
|
2635
2784
|
hostPrintOut.append(f"│░ RETURN CODE: {host['returncode']}")
|
|
2636
2785
|
lineBag.add((prevLine,f"{host['returncode']}"))
|
|
2786
|
+
max_length = max(max_length, max(map(len, hostPrintOut)))
|
|
2637
2787
|
outputs_by_hostname[host['name']] = hostPrintOut
|
|
2638
2788
|
line_bag_by_hostname[host['name']] = lineBag
|
|
2639
2789
|
hostnames_by_line_bag_len.setdefault(len(lineBag), set()).add(host['name'])
|
|
2640
|
-
return outputs_by_hostname, line_bag_by_hostname, hostnames_by_line_bag_len, sorted(hostnames_by_line_bag_len)
|
|
2790
|
+
return outputs_by_hostname, line_bag_by_hostname, hostnames_by_line_bag_len, sorted(hostnames_by_line_bag_len), min(max_length+2,terminal_width)
|
|
2641
2791
|
|
|
2642
2792
|
def form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_bag_by_hostname, diff_display_threshold):
|
|
2643
2793
|
merge_groups = []
|
|
@@ -2658,7 +2808,7 @@ def form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_le
|
|
|
2658
2808
|
continue
|
|
2659
2809
|
if can_merge(this_line_bag, line_bag_by_hostname[other_hostname], diff_display_threshold):
|
|
2660
2810
|
merge_group.append(other_hostname)
|
|
2661
|
-
hostnames_by_line_bag_len
|
|
2811
|
+
hostnames_by_line_bag_len.get(line_bag_len, set()).discard(this_hostname)
|
|
2662
2812
|
hostnames_by_line_bag_len[other_line_bag_len].remove(other_hostname)
|
|
2663
2813
|
if not hostnames_by_line_bag_len[other_line_bag_len]:
|
|
2664
2814
|
del hostnames_by_line_bag_len[other_line_bag_len]
|
|
@@ -2714,22 +2864,26 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
|
|
|
2714
2864
|
eprint("Warning: diff_display_threshold should be a float between 0 and 1. Setting to default value of 0.9")
|
|
2715
2865
|
diff_display_threshold = 0.9
|
|
2716
2866
|
terminal_length = get_terminal_size()[0]
|
|
2717
|
-
outputs_by_hostname, line_bag_by_hostname, hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys = get_host_raw_output(hosts)
|
|
2867
|
+
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)
|
|
2718
2868
|
merge_groups = form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_bag_by_hostname, diff_display_threshold)
|
|
2719
2869
|
# get the remaining hostnames in the hostnames_by_line_bag_len
|
|
2720
2870
|
remaining_hostnames = set()
|
|
2721
2871
|
for hostnames in hostnames_by_line_bag_len.values():
|
|
2722
2872
|
remaining_hostnames.update(hostnames)
|
|
2723
|
-
outputs = mergeOutputs(outputs_by_hostname, merge_groups,remaining_hostnames, diff_display_threshold)
|
|
2873
|
+
outputs = mergeOutputs(outputs_by_hostname, merge_groups,remaining_hostnames, diff_display_threshold,line_length)
|
|
2724
2874
|
if keyPressesIn[-1]:
|
|
2725
2875
|
CMDsOut = [''.join(cmd).encode(encoding=encoding,errors='backslashreplace').decode(encoding=encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in keyPressesIn if cmd]
|
|
2726
|
-
outputs.append("├─ User Inputs:".ljust(
|
|
2727
|
-
|
|
2876
|
+
outputs.append("├─ User Inputs:".ljust(line_length -1,'─')+'┤')
|
|
2877
|
+
cmdOut = []
|
|
2878
|
+
for line in CMDsOut:
|
|
2879
|
+
cmdOut.extend(textwrap.wrap(line, width=line_length-1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
2880
|
+
initial_indent='│ ', subsequent_indent='│-'))
|
|
2881
|
+
outputs.extend(cmd.ljust(line_length -1)+'│' for cmd in cmdOut)
|
|
2728
2882
|
keyPressesIn[-1].clear()
|
|
2729
2883
|
if quiet and not outputs:
|
|
2730
2884
|
rtnStr = 'Success'
|
|
2731
2885
|
else:
|
|
2732
|
-
rtnStr = '\n'.join(outputs + [('
|
|
2886
|
+
rtnStr = '\n'.join(outputs + [('\033[0m└'+'─'*(line_length-2)+'┘')])
|
|
2733
2887
|
return rtnStr
|
|
2734
2888
|
|
|
2735
2889
|
def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
@@ -3356,6 +3510,7 @@ def generate_default_config(args):
|
|
|
3356
3510
|
'DEFAULT_DIFF_DISPLAY_THRESHOLD': args.diff_display_threshold,
|
|
3357
3511
|
'SSH_STRICT_HOST_KEY_CHECKING': SSH_STRICT_HOST_KEY_CHECKING,
|
|
3358
3512
|
'ERROR_MESSAGES_TO_IGNORE': ERROR_MESSAGES_TO_IGNORE,
|
|
3513
|
+
'FORCE_TRUECOLOR': args.force_truecolor,
|
|
3359
3514
|
}
|
|
3360
3515
|
|
|
3361
3516
|
def write_default_config(args,CONFIG_FILE = None):
|
|
@@ -3400,7 +3555,7 @@ def write_default_config(args,CONFIG_FILE = None):
|
|
|
3400
3555
|
def get_parser():
|
|
3401
3556
|
global _binPaths
|
|
3402
3557
|
parser = argparse.ArgumentParser(description=f'Run a command on multiple hosts, Use #HOST# or #HOSTNAME# to replace the host name in the command. Config file chain: {CONFIG_FILE_CHAIN!r}',
|
|
3403
|
-
epilog=f'Found bins: {list(_binPaths.values())}\n Missing bins: {_binCalled - set(_binPaths.keys())}')
|
|
3558
|
+
epilog=f'Found bins: {list(_binPaths.values())}\n Missing bins: {_binCalled - set(_binPaths.keys())}\n Terminal color capability: {get_terminal_color_capability()}',)
|
|
3404
3559
|
parser.add_argument('hosts', metavar='hosts', type=str, nargs='?', help=f'Hosts to run the command on, use "," to seperate hosts. (default: {DEFAULT_HOSTS})',default=DEFAULT_HOSTS)
|
|
3405
3560
|
parser.add_argument('commands', metavar='commands', type=str, nargs='*',default=None,help='the command to run on the hosts / the destination of the files #HOST# or #HOSTNAME# will be replaced with the host name.')
|
|
3406
3561
|
parser.add_argument('-u','--username', type=str,help=f'The general username to use to connect to the hosts. Will get overwrote by individual username@host if specified. (default: {DEFAULT_USERNAME})',default=DEFAULT_USERNAME)
|
|
@@ -3451,6 +3606,7 @@ def get_parser():
|
|
|
3451
3606
|
parser.add_argument('--script', action='store_true', help='Run the command in script mode, short for -SCRIPT or --no_watch --skip_unreachable --no_env --no_history --greppable --error_only')
|
|
3452
3607
|
parser.add_argument('-e','--encoding', type=str, help=f'The encoding to use for the output. (default: {DEFAULT_ENCODING})', default=DEFAULT_ENCODING)
|
|
3453
3608
|
parser.add_argument('-ddt','--diff_display_threshold', type=float, help=f'The threshold of lines to display the diff when files differ. Set to 0 to always display the diff. (default: {DEFAULT_DIFF_DISPLAY_THRESHOLD})', default=DEFAULT_DIFF_DISPLAY_THRESHOLD)
|
|
3609
|
+
parser.add_argument('--force_truecolor', action='store_true', help=f'Force truecolor output even when not in a truecolor terminal. (default: {FORCE_TRUECOLOR})', default=FORCE_TRUECOLOR)
|
|
3454
3610
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
3455
3611
|
return parser
|
|
3456
3612
|
|
|
@@ -3553,6 +3709,7 @@ def set_global_with_args(args):
|
|
|
3553
3709
|
global DEFAULT_IPMI_USERNAME
|
|
3554
3710
|
global DEFAULT_IPMI_PASSWORD
|
|
3555
3711
|
global DEFAULT_DIFF_DISPLAY_THRESHOLD
|
|
3712
|
+
global FORCE_TRUECOLOR
|
|
3556
3713
|
_emo = False
|
|
3557
3714
|
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
3558
3715
|
_env_file = args.env_file
|
|
@@ -3565,6 +3722,7 @@ def set_global_with_args(args):
|
|
|
3565
3722
|
if args.ipmi_password:
|
|
3566
3723
|
DEFAULT_IPMI_PASSWORD = args.ipmi_password
|
|
3567
3724
|
DEFAULT_DIFF_DISPLAY_THRESHOLD = args.diff_display_threshold
|
|
3725
|
+
FORCE_TRUECOLOR = args.force_truecolor
|
|
3568
3726
|
|
|
3569
3727
|
#%% ------------ Wrapper Block ----------------
|
|
3570
3728
|
def main():
|
|
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
|
|
File without changes
|