From 0633cda29958cafc38a10282420023eb3d5a5103 Mon Sep 17 00:00:00 2001 From: James Antill Date: Tue, 10 Feb 2026 17:21:18 -0500 Subject: [PATCH] updates+uptimes: Minor UI tweaks, less hacky sort. Signed-off-by: James Antill --- files/scripts/updates-uptime-cmd.py | 124 ++++++++++++++++++---------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/files/scripts/updates-uptime-cmd.py b/files/scripts/updates-uptime-cmd.py index c2aa723879..6091f0d6eb 100755 --- a/files/scripts/updates-uptime-cmd.py +++ b/files/scripts/updates-uptime-cmd.py @@ -50,9 +50,6 @@ conf_term_keyword = 'underline' conf_term_time = 'underline' conf_term_title = 'italic' -# Use version cmp() for hostnames. -conf_verscmp = True - # Use _ instead of , for number seperator. conf_num_sep_ = False @@ -152,7 +149,7 @@ def _user_conf_line(line): key = 'conf_' + key.strip().lower() if key not in globals(): - print(" Error: Configuration not found: ", key, file=sys.stderr) + print(" Warn: Configuration not found: ", key, file=sys.stderr) return if False: pass @@ -189,42 +186,85 @@ def _user_conf_line(line): print(" Error: Configuration ", key,'bad op', file=sys.stderr) return -# This isn't fast but it's SMALL: -# Sort as: ABC1, ABC2, ABC10b, ... -def verscmp(x, y): - if not conf_verscmp: - if x == y: - return 0 - if x > y: - return 1 - return -1 - xc = re.split(r'(\d+|\W+)', x) - yc = re.split(r'(\d+|\W+)', y) - while xc and yc: - if xc[0] == yc[0]: - xc.pop(0) - yc.pop(0) - continue +# This is kind of fast and kind of small. No re, and no allocation. +# Sort as: 0, 00, 000, 01, 011, 1, 11, a01, a1, z01, z1, etc. +def natcmp(x, y): + """ Natural sort string comparison. + https://en.wikipedia.org/wiki/Natural_sort_order + Aka. vercmp() """ - if xc[0].isnumeric(): - if not yc[0].isnumeric(): - return 1 - nx = int(xc[0]) - ny = int(yc[0]) - if nx != ny: # don't make 0 == 00 ... we aren't rpm. - return nx - ny - elif yc[0].isnumeric(): + def _cmp_xy_mix(): # One is a digit, the other isn't. + if inum is not None: # 0/1 vs. x/. + return 1 + if x[i] > y[i]: + return 1 + else: return -1 - # Neither numeric, but also 0 == 00 BS - if xc[0] > yc[0]: - return 1 + inum = None + check_zeros = False + for i in range(min(len(x), len(y))): + if x[i] in "0123456789" and y[i] not in "0123456789": + return _cmp_xy_mix() + if x[i] not in "0123456789" and y[i] in "0123456789": + return _cmp_xy_mix() + + if x[i] in "0123456789": # Both are digits... + if inum is None: + check_zeros = True + inum = 0 + + if check_zeros: # Leading zeros... (0 < 00 < 01 < 011 < 1 < 11) + if x[i] == '0' and y[i] == '0': + continue + elif x[i] == '0': + return -1 + elif y[i] == '0': + return 1 + else: + check_zeros = False + + # If we are already in a number, we only care about the length or + # the first digit that is different. + if inum != 0: + continue + + if x[i] == y[i]: + continue + + # Non-zero first digit, Eg. 7 < 9 + inum = int(x[i]) - int(y[i]) + continue + + # Both are not digits... + if inum is not None and inum != 0: + return inum + inum = None + + # Can be equal + if x[i] > y[i]: + return 1 + if x[i] < y[i]: + return -1 + + if len(x) > len(y): + if inum is not None and inum != 0 and x[i+1] not in "0123456789": + return inum + return 1 + if len(x) < len(y): + if inum is not None and inum != 0 and y[i+1] not in "0123456789": + return inum return -1 - return len(xc) - len(yc) + + if inum is None: # Same length, not in a num. + assert x == y + return 0 # So the strings are equal. + + return inum -class VerscmpString(): +class NatCmp(): __slots__ = ['s',] def __init__(self, s): self.s = s @@ -236,16 +276,14 @@ class VerscmpString(): return self.s == other.s def __gt__(self, other): - ret = verscmp(self.s, other.s) + ret = natcmp(self.s, other.s) if ret > 0: return True - if False and ret < 0: - return False return False -# Given a list of strings, sort them using verscmp() -def _verscmp_strings(xs): - for ret in sorted(VerscmpString(x) for x in xs): +# Given a list of strings, sort them using natcmp() +def nat_sorted(xs): + for ret in sorted(NatCmp(x) for x in xs): yield ret.s @@ -490,7 +528,7 @@ class Host(): return True def __gt__(self, other): - ret = verscmp(self.name, other.name) + ret = natcmp(self.name, other.name) if ret > 0: return True if ret < 0: @@ -1337,7 +1375,7 @@ def _cmd_stats(args): max_nhosts_lvl_2 = 0 max_update_lvl_2 = 0 max_uptime_lvl_2 = 0 - for osi in _verscmp_strings(osdata['hosts']): + for osi in nat_sorted(osdata['hosts']): if '/' not in osi: max_nhosts_lvl_1 = max(max_nhosts_lvl_1, osdata['hosts'][osi]) supd = osdata['updates'][osi] / osdata['hosts'][osi] @@ -1385,7 +1423,7 @@ def _cmd_stats(args): suf = _ui_t_high(suf) return uiosi, uinhosts, uiupdates, uiuptimes, uinpdates, uinptimes, suf - for osi in _verscmp_strings(osdata['hosts']): + for osi in nat_sorted(osdata['hosts']): if '/' not in osi: if len(osdata['vers'][osi]) == 1: subprefix = '' @@ -2003,7 +2041,7 @@ utf8: {conf_utf8} {_conf_utf8_diff_os} = Different OS information, but machine id is the same {_conf_utf8_diff_hw} = Machine id is different - {_hlp_als("host")} + {_hlp_als("hosts")} Eg. {prog} {args.hcmd} {prog} {args.hcmd} 'batcave*' {prog} {args.hcmd} 'batcave*' 'noc*'