Skip to content

Commit 995c301

Browse files
committed
cli: Print pretty tables
1 parent 453dac1 commit 995c301

6 files changed

Lines changed: 122 additions & 11 deletions

File tree

bbblb/cli/__init__.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
import functools
33
import importlib
44
import pkgutil
5+
import typing
56
from bbblb.services import bootstrap
67
from bbblb.settings import ConfigError, BBBLBConfig
78
import click
89
import os
10+
import tabulate
11+
import json
912

1013

1114
def async_command():
@@ -49,6 +52,55 @@ def to_info_dict(self):
4952
return info_dict
5053

5154

55+
class Table:
56+
formats = ["simple", "plain", "raw", "json"]
57+
option = click.option(
58+
"--table-format",
59+
type=click.Choice(formats),
60+
default=formats[0],
61+
help="Change the result table format.",
62+
)
63+
64+
def __init__(self):
65+
self._rows: list[list[typing.Any]] = []
66+
self._headers = {}
67+
68+
def headers(self, **headers):
69+
"""Map column names to human readable labels"""
70+
self._headers.update(headers)
71+
72+
def row(self, **values):
73+
"""Add a row to the table, pamming column names ot values.
74+
75+
Missing columns are stored as `None`. Previously unknown columns
76+
are added to the table.
77+
"""
78+
for key in values:
79+
if key not in self._headers:
80+
self._headers[key] = key.title()
81+
for row in self._rows:
82+
row.append(None)
83+
self._rows.append([values.get(column, None) for column in self._headers])
84+
85+
def print(self, format="simple"):
86+
if format == "json":
87+
keys = list(self._headers)
88+
for row in self._rows:
89+
click.echo(json.dumps(dict(zip(keys, row))))
90+
elif format == "raw":
91+
for row in self._rows:
92+
click.echo("\t".join(map(str, row)))
93+
else:
94+
click.echo(
95+
tabulate.tabulate(
96+
self._rows,
97+
list(self._headers.values()),
98+
tablefmt=format,
99+
floatfmt=".2f",
100+
)
101+
)
102+
103+
52104
@click.group(
53105
name="bbblb",
54106
context_settings=dict(show_default=True, help_option_names=["--help", "-h"]),

bbblb/cli/server.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from bbblb.services.bbb import BBBHelper
1010
from bbblb.services.db import DBContext
1111

12-
from . import main, async_command
12+
from . import Table, main, async_command
1313

1414

1515
@main.group()
@@ -177,26 +177,39 @@ async def _end_meeting(obj: ServiceRegistry, meeting: model.Meeting):
177177

178178

179179
@server.command()
180+
@Table.option
180181
@async_command()
181-
async def list(obj: ServiceRegistry):
182+
async def list(obj: ServiceRegistry, table_format: str):
182183
"""List all servers with their secrets."""
183184
db = await obj.use(DBContext)
184185

186+
tbl = Table()
185187
async with db.session() as session:
186188
stmt = model.Server.select().order_by(model.Server.domain)
187189
for server in (await session.execute(stmt)).scalars():
188-
out = f"{server.domain} {server.secret}"
189-
click.echo(out)
190+
tbl.row(server=server.domain, secret=server.secret)
191+
tbl.print(format=table_format)
190192

191193

192194
@server.command()
195+
@Table.option
193196
@async_command()
194-
async def stats(obj: ServiceRegistry):
197+
async def stats(obj: ServiceRegistry, table_format):
195198
"""Show server statistics (state, health, load)."""
196199
db = await obj.use(DBContext)
197200

201+
tbl = Table()
198202
async with db.session() as session:
199203
stmt = model.Server.select().order_by(model.Server.domain)
200204
for server in (await session.execute(stmt)).scalars():
201-
out = f"{server.domain} enabled={server.enabled} health={server.health.name.lower()} load={server.load:.1f}"
202-
click.echo(out)
205+
tbl.row(
206+
server=server.domain,
207+
enabled=server.enabled,
208+
state=server.health.name.lower(),
209+
meetings=server.stats.get("meetings", 0),
210+
users=server.stats.get("users", 0),
211+
voice=server.stats.get("voice", 0),
212+
video=server.stats.get("video", 0),
213+
load=server.load,
214+
)
215+
tbl.print(format=table_format)

bbblb/cli/tenant.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from bbblb.settings import BBBLBConfig
99

10-
from . import main, async_command
10+
from . import Table, main, async_command
1111

1212

1313
@main.group()
@@ -101,12 +101,19 @@ async def disable(obj: ServiceRegistry, name: str, nuke: bool):
101101

102102

103103
@tenant.command("list")
104+
@Table.option
104105
@async_command()
105-
async def list_(obj: ServiceRegistry):
106+
async def list_(obj: ServiceRegistry, table_format: str):
106107
"""List all tenants with their realms and secrets."""
107108
db = await obj.use(DBContext)
109+
tbl = Table()
108110
async with db.session() as session:
109111
tenants = (await session.execute(model.Tenant.select())).scalars()
110112
for tenant in tenants:
111-
out = f"{tenant.name} {tenant.realm} {tenant.secret}"
112-
click.echo(out)
113+
tbl.row(
114+
tenant=tenant.name,
115+
realm=tenant.realm,
116+
enabled=tenant.enabled,
117+
secret=tenant.secret,
118+
)
119+
tbl.print(format=table_format)

docs/_click.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,13 +383,31 @@ server list
383383

384384
List all servers with their secrets.
385385

386+
.. table:: Options
387+
:width: 100%
388+
389+
====================================== ==================================================
390+
Option Help
391+
====================================== ==================================================
392+
--table-format [simple|plain|raw|json] Change the result table format. [default: simple]
393+
====================================== ==================================================
394+
386395
server stats
387396
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
388397

389398
``Usage: bbblb server stats [OPTIONS]``
390399

391400
Show server statistics (state, health, load).
392401

402+
.. table:: Options
403+
:width: 100%
404+
405+
====================================== ==================================================
406+
Option Help
407+
====================================== ==================================================
408+
--table-format [simple|plain|raw|json] Change the result table format. [default: simple]
409+
====================================== ==================================================
410+
393411
state
394412
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
395413

@@ -529,3 +547,12 @@ tenant list
529547

530548
List all tenants with their realms and secrets.
531549

550+
.. table:: Options
551+
:width: 100%
552+
553+
====================================== ==================================================
554+
Option Help
555+
====================================== ==================================================
556+
--table-format [simple|plain|raw|json] Change the result table format. [default: simple]
557+
====================================== ==================================================
558+

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies = [
2222
"pyjwt>=2.10.1",
2323
"sqlalchemy>=2.0.44",
2424
"starlette>=0.48.0",
25+
"tabulate>=0.9.0",
2526
]
2627

2728
[project.optional-dependencies]

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)