From e0ea554b7b4b16577e8ddd9a2b1bcf94c3b8d1e2 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 2 Jan 2026 19:27:52 +0100 Subject: [PATCH] statd: update SimpleTable with flexible columns and fix spacing This patch adds support for flexible columns. When a column is marked as flexible it means it can be stretched when printing the table to an optional minimal table width. Multiple columns can be marked and when this occurs the padding is applied equally to all columns. Also, make sure to only add 2 char padding between columns, not always at the end, or we will require larger terminal than necessary. Signed-off-by: Joachim Wiberg --- src/statd/python/cli_pretty/cli_pretty.py | 92 +++++++++++++++++++---- test/case/statd/system/cli/show-services | 4 +- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index c67a4e0f..61524531 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -283,17 +283,20 @@ class PadFirewall: class Column: """Column definition for SimpleTable""" - def __init__(self, name, align='left', formatter=None): + def __init__(self, name, align='left', formatter=None, flexible=False): self.name = name self.align = align self.formatter = formatter + self.flexible = flexible class SimpleTable: """Simple table formatter that handles ANSI colors correctly and calculates dynamic column widths""" - def __init__(self, columns): + def __init__(self, columns, min_width=None): self.columns = columns self.rows = [] + self.min_width = min_width + self._column_widths = None # Cache calculated widths @staticmethod def visible_width(text): @@ -308,12 +311,58 @@ class SimpleTable: raise ValueError(f"Expected {len(self.columns)} values, got {len(values)}") self.rows.append(values) + def width(self): + """Calculate and return total table width""" + if self._column_widths is None: + self._column_widths = self._calculate_column_widths() + + # Sum column widths + 2-char separator between columns + total = sum(self._column_widths) + if self._column_widths: + # Separators only between columns, not after the last one + total += (len(self._column_widths) - 1) * 2 + + return total + + def adjust_padding(self, width): + """Distribute padding to width evenly across flexible columns""" + if self._column_widths is None: + self._column_widths = self._calculate_column_widths() + + current_width = self.width() + extra_width = width - current_width + + if extra_width <= 0: + return # Already at or above target + + # Find flexible columns + flex_indices = [i for i, col in enumerate(self.columns) if col.flexible] + + if not flex_indices: + return # No flexible columns to expand + + # Distribute evenly + per_column = extra_width // len(flex_indices) + remainder = extra_width % len(flex_indices) + + for i, idx in enumerate(flex_indices): + self._column_widths[idx] += per_column + # Give remainder to first columns + if i < remainder: + self._column_widths[idx] += 1 + def print(self, styled=True): """Calculate widths and print complete table""" - column_widths = self._calculate_column_widths() - print(self._format_header(column_widths, styled)) + if self._column_widths is None: + self._column_widths = self._calculate_column_widths() + + # Apply minimum width if specified + if self.min_width: + self.adjust_padding(self.min_width) + + print(self._format_header(self._column_widths, styled)) for row_data in self.rows: - print(self._format_row(row_data, column_widths)) + print(self._format_row(row_data, self._column_widths)) def _calculate_column_widths(self): """Calculate maximum width needed for each column""" @@ -332,24 +381,29 @@ class SimpleTable: header_parts = [] for i, column in enumerate(self.columns): width = column_widths[i] - if column.align == 'right': - header_parts.append(f"{column.name:>{width}} ") - else: - header_parts.append(f"{column.name:{width}} ") + # Add separator " " only between columns, not after the last one + is_last = (i == len(self.columns) - 1) + separator = "" if is_last else " " - header_str = ''.join(header_parts).rstrip() + if column.align == 'right': + header_parts.append(f"{column.name:>{width}}{separator}") + else: + header_parts.append(f"{column.name:{width}}{separator}") + + header_str = ''.join(header_parts) return Decore.invert(header_str) if styled else header_str def _format_row(self, row_data, column_widths): """Format a single data row""" row_parts = [] for i, (value, column) in enumerate(zip(row_data, self.columns)): - formatted_value = self._format_column_value(value, column, column_widths[i]) + is_last = (i == len(self.columns) - 1) + formatted_value = self._format_column_value(value, column, column_widths[i], is_last) row_parts.append(formatted_value) - return ''.join(row_parts).rstrip() + return ''.join(row_parts) - def _format_column_value(self, value, column, width): + def _format_column_value(self, value, column, width, is_last=False): """Format a single column value with proper alignment""" if column.formatter: value = column.formatter(value) @@ -357,12 +411,18 @@ class SimpleTable: value_str = str(value) visible_len = self.visible_width(value_str) - if column.align == 'right': + # Add separator " " only between columns, not after the last one + separator = "" if is_last else " " + + # Don't pad the last column to avoid trailing spaces + if is_last: + return value_str + elif column.align == 'right': padding = width - visible_len - return ' ' * max(0, padding) + value_str + ' ' + return ' ' * max(0, padding) + value_str + separator else: padding = width - visible_len - return value_str + ' ' * max(0, padding) + ' ' + return value_str + ' ' * max(0, padding) + separator class Decore(): diff --git a/test/case/statd/system/cli/show-services b/test/case/statd/system/cli/show-services index 322c4b78..35c6bd67 100644 --- a/test/case/statd/system/cli/show-services +++ b/test/case/statd/system/cli/show-services @@ -1,4 +1,4 @@ -NAME STATUS PID MEM UP RST DESCRIPTION +NAME STATUS PID MEM UP RST DESCRIPTION  udevd running 1185 23m 0 Device event daemon (udev) dbus running 2248 23m 0 D-Bus message bus daemon confd running 3039 23m 0 Configuration daemon @@ -7,7 +7,7 @@ dnsmasq running 2249 23m 0 DHCP/DNS proxy tty:hvc0 running 3559 23m 0 Getty on hvc0 iitod running 2340 23m 0 LED daemon klishd running 3560 23m 0 CLI backend daemon -mdns-alias running 3617 23m 0 mDNS alias advertiser +mdns-alias running 3617 23m 0 mDNS alias advertiser mstpd stopped 0 Spanning Tree daemon rauc running 3564 23m 0 Software update service resolvconf done 2 Update DNS configuration