Skip to content

Commit faebf70

Browse files
committed
cli: use dynamic column widths in simple table
- Remove fixed lengths from Column class - Add dynamic width calculation to SimpleTable - Update service table to use new batched printing Signed-off-by: Richard Alpe <[email protected]>
1 parent 72ffbf6 commit faebf70

File tree

2 files changed

+89
-69
lines changed

2 files changed

+89
-69
lines changed

src/statd/python/cli_pretty/cli_pretty.py

Lines changed: 65 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -283,17 +283,17 @@ def table_width(cls):
283283

284284
class Column:
285285
"""Column definition for SimpleTable"""
286-
def __init__(self, name, length, align='left', formatter=None):
287-
self.name = name # Header text
288-
self.width = length # Max visible data length (excluding ANSI codes)
289-
self.align = align # 'left' or 'right' (defaults to 'left')
290-
self.formatter = formatter # Optional function to format values
286+
def __init__(self, name, align='left', formatter=None):
287+
self.name = name
288+
self.align = align
289+
self.formatter = formatter
291290

292291
class SimpleTable:
293-
"""Simple table formatter that handles ANSI colors correctly"""
292+
"""Simple table formatter that handles ANSI colors correctly and calculates dynamic column widths"""
294293

295294
def __init__(self, columns):
296295
self.columns = columns
296+
self.rows = []
297297

298298
@staticmethod
299299
def visible_width(text):
@@ -302,48 +302,68 @@ def visible_width(text):
302302
clean_text = re.sub(ansi_pattern, '', str(text))
303303
return len(clean_text)
304304

305-
def _format_column(self, value, column):
306-
"""Format a single column value with proper alignment
305+
def row(self, *values):
306+
"""Store row data for later formatting"""
307+
if len(values) != len(self.columns):
308+
raise ValueError(f"Expected {len(self.columns)} values, got {len(values)}")
309+
self.rows.append(values)
307310

308-
The column length specifies max expected data length.
309-
Framework automatically adds 1 space for column separation.
310-
"""
311-
if column.formatter:
312-
value = column.formatter(value)
311+
def print(self, styled=True):
312+
"""Calculate widths and print complete table"""
313+
column_widths = self._calculate_column_widths()
314+
print(self._format_header(column_widths, styled))
315+
for row_data in self.rows:
316+
print(self._format_row(row_data, column_widths))
313317

314-
value_str = str(value)
315-
visible_len = self.visible_width(value_str)
318+
def _calculate_column_widths(self):
319+
"""Calculate maximum width needed for each column"""
320+
widths = [self.visible_width(col.name) for col in self.columns]
316321

317-
if column.align == 'right':
318-
padding = column.width - visible_len
319-
return ' ' * max(0, padding) + value_str + ' '
320-
else: # left alignment (default)
321-
padding = column.width - visible_len
322-
return value_str + ' ' * max(0, padding) + ' '
322+
for row_data in self.rows:
323+
for i, (value, column) in enumerate(zip(row_data, self.columns)):
324+
formatted_value = column.formatter(value) if column.formatter else value
325+
value_width = self.visible_width(str(formatted_value))
326+
widths[i] = max(widths[i], value_width)
327+
328+
return widths
323329

324-
def header(self, styled=True):
330+
def _format_header(self, column_widths, styled=True):
325331
"""Generate formatted header row"""
326332
header_parts = []
327-
for column in self.columns:
333+
for i, column in enumerate(self.columns):
334+
width = column_widths[i]
328335
if column.align == 'right':
329-
header_parts.append(f"{column.name:>{column.width}} ")
330-
else: # left alignment (default)
331-
header_parts.append(f"{column.name:{column.width}} ")
336+
header_parts.append(f"{column.name:>{width}} ")
337+
else:
338+
header_parts.append(f"{column.name:{width}} ")
332339

333-
header_str = ''.join(header_parts)
340+
header_str = ''.join(header_parts).rstrip()
334341
return Decore.invert(header_str) if styled else header_str
335342

336-
def row(self, *values):
337-
"""Generate formatted data row"""
338-
if len(values) != len(self.columns):
339-
raise ValueError(f"Expected {len(self.columns)} values, got {len(values)}")
340-
343+
def _format_row(self, row_data, column_widths):
344+
"""Format a single data row"""
341345
row_parts = []
342-
for value, column in zip(values, self.columns):
343-
row_parts.append(self._format_column(value, column))
346+
for i, (value, column) in enumerate(zip(row_data, self.columns)):
347+
formatted_value = self._format_column_value(value, column, column_widths[i])
348+
row_parts.append(formatted_value)
344349

345350
return ''.join(row_parts).rstrip()
346351

352+
def _format_column_value(self, value, column, width):
353+
"""Format a single column value with proper alignment"""
354+
if column.formatter:
355+
value = column.formatter(value)
356+
357+
value_str = str(value)
358+
visible_len = self.visible_width(value_str)
359+
360+
if column.align == 'right':
361+
padding = width - visible_len
362+
return ' ' * max(0, padding) + value_str + ' '
363+
else:
364+
padding = width - visible_len
365+
return value_str + ' ' * max(0, padding) + ' '
366+
347367

348368
class Decore():
349369
@staticmethod
@@ -1781,17 +1801,15 @@ def show_services(json):
17811801
# copied so I left a lot of comments. If you copy it feel free
17821802
# to be less verbose..
17831803
service_table = SimpleTable([
1784-
Column('NAME', 15, 'left'), # Max service name length
1785-
Column('STATUS', 10, 'left'), # Max status text length (e.g., "running")
1786-
Column('PID', 7, 'right'), # Max PID digits
1787-
Column('MEM', 6, 'right'), # Max memory string (e.g., "123.4M")
1788-
Column('UP', 4, 'right'), # Max uptime string (e.g., "3d")
1789-
Column('RST', 3, 'right'), # Max restart count digits
1790-
Column('DESCRIPTION', 30) # Last column needs no padding
1804+
Column('NAME'),
1805+
Column('STATUS'),
1806+
Column('PID', 'right'),
1807+
Column('MEM', 'right'),
1808+
Column('UP', 'right'),
1809+
Column('RST', 'right'),
1810+
Column('DESCRIPTION')
17911811
])
17921812

1793-
print(service_table.header())
1794-
17951813
for svc in services:
17961814
name = svc.get('name', '')
17971815
status = svc.get('status', '')
@@ -1815,8 +1833,10 @@ def show_services(json):
18151833
memory_str = format_memory_bytes(memory_bytes)
18161834
uptime_str = format_uptime_seconds(uptime_secs)
18171835

1818-
print(service_table.row(name, status_str, pid_str, memory_str,
1819-
uptime_str, restart_count, description))
1836+
service_table.row(name, status_str, pid_str, memory_str,
1837+
uptime_str, restart_count, description)
1838+
1839+
service_table.print()
18201840

18211841

18221842
def show_hardware(json):
Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
NAME STATUS PID MEM UP RST DESCRIPTION 
2-
udevd running 1185 23m 0 Device event daemon (udev)
3-
dbus running 2248 23m 0 D-Bus message bus daemon
4-
confd running 3039 23m 0 Configuration daemon
5-
netopeer running 3548 23m 0 NETCONF server
6-
dnsmasq running 2249 23m 0 DHCP/DNS proxy
7-
tty:hvc0 running 3559 23m 0 Getty on hvc0
8-
iitod running 2340 23m 0 LED daemon
9-
klishd running 3560 23m 0 CLI backend daemon
10-
mdns-alias running 3617 23m 0 mDNS alias advertiser
11-
mstpd stopped 0 Spanning Tree daemon
12-
rauc running 3564 23m 0 Software update service
13-
resolvconf done 2 Update DNS configuration
14-
statd running 3472 23m 0 Status daemon
15-
staticd running 3653 23m 0 Static routing daemon
16-
syslogd running 2241 23m 0 System log daemon
17-
watchdogd running 2242 23m 0 System watchdog daemon
18-
zebra running 3587 23m 0 Zebra routing daemon
19-
mdns running 3616 23m 0 Avahi mDNS-SD daemon
20-
chronyd running 3618 23m 0 Chrony NTP v3/v4 daemon
21-
lldpd running 3633 23m 0 LLDP daemon (IEEE 802.1ab)
22-
nginx running 3635 23m 0 Web server
23-
rousette running 3636 23m 0 RESTCONF server
24-
sshd running 3641 23m 0 OpenSSH daemon
1+
NAME STATUS PID MEM UP RST DESCRIPTION
2+
udevd running 1185 23m 0 Device event daemon (udev)
3+
dbus running 2248 23m 0 D-Bus message bus daemon
4+
confd running 3039 23m 0 Configuration daemon
5+
netopeer running 3548 23m 0 NETCONF server
6+
dnsmasq running 2249 23m 0 DHCP/DNS proxy
7+
tty:hvc0 running 3559 23m 0 Getty on hvc0
8+
iitod running 2340 23m 0 LED daemon
9+
klishd running 3560 23m 0 CLI backend daemon
10+
mdns-alias running 3617 23m 0 mDNS alias advertiser
11+
mstpd stopped 0 Spanning Tree daemon
12+
rauc running 3564 23m 0 Software update service
13+
resolvconf done 2 Update DNS configuration
14+
statd running 3472 23m 0 Status daemon
15+
staticd running 3653 23m 0 Static routing daemon
16+
syslogd running 2241 23m 0 System log daemon
17+
watchdogd running 2242 23m 0 System watchdog daemon
18+
zebra running 3587 23m 0 Zebra routing daemon
19+
mdns running 3616 23m 0 Avahi mDNS-SD daemon
20+
chronyd running 3618 23m 0 Chrony NTP v3/v4 daemon
21+
lldpd running 3633 23m 0 LLDP daemon (IEEE 802.1ab)
22+
nginx running 3635 23m 0 Web server
23+
rousette running 3636 23m 0 RESTCONF server
24+
sshd running 3641 23m 0 OpenSSH daemon

0 commit comments

Comments
 (0)