devnomads-cli 0.5.2__py3-none-any.whl → 0.5.4__py3-none-any.whl
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.
- {devnomads_cli-0.5.2.dist-info → devnomads_cli-0.5.4.dist-info}/METADATA +1 -1
- devnomads_cli-0.5.4.dist-info/RECORD +7 -0
- dncli.py +103 -7
- devnomads_cli-0.5.2.dist-info/RECORD +0 -7
- {devnomads_cli-0.5.2.dist-info → devnomads_cli-0.5.4.dist-info}/WHEEL +0 -0
- {devnomads_cli-0.5.2.dist-info → devnomads_cli-0.5.4.dist-info}/entry_points.txt +0 -0
- {devnomads_cli-0.5.2.dist-info → devnomads_cli-0.5.4.dist-info}/licenses/LICENSE +0 -0
- {devnomads_cli-0.5.2.dist-info → devnomads_cli-0.5.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
dncli.py,sha256=3WdV9xsEWXaJnaqyzCviKYV7hdADMZa2v69afkMPvOw,124105
|
|
2
|
+
devnomads_cli-0.5.4.dist-info/licenses/LICENSE,sha256=WPXQ_lmpr3tVzTD9Z6VTiBgFnFey-cEwEYoG4HlmbxY,1066
|
|
3
|
+
devnomads_cli-0.5.4.dist-info/METADATA,sha256=r1_WaqFGTmdROvJL3RdfCZX8xI8S751POgZ_Pygtn4U,5268
|
|
4
|
+
devnomads_cli-0.5.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
devnomads_cli-0.5.4.dist-info/entry_points.txt,sha256=irLaKiGtIzEKi87exmXAg0srUmoDK31fL0SlqJasAEU,69
|
|
6
|
+
devnomads_cli-0.5.4.dist-info/top_level.txt,sha256=rFWIJDQgL3jA2q6b2GAqZgCknDED4WLxNcHTktuWS1I,6
|
|
7
|
+
devnomads_cli-0.5.4.dist-info/RECORD,,
|
dncli.py
CHANGED
|
@@ -44,6 +44,7 @@ from devnomads.api import Client as ApiClient
|
|
|
44
44
|
from devnomads.api import DevNomadsError
|
|
45
45
|
from devnomads.api.client import _unwrap as _lib_unwrap
|
|
46
46
|
from devnomads.dns import Dns, challenge_name
|
|
47
|
+
from rich import box
|
|
47
48
|
from rich.console import Console
|
|
48
49
|
from rich.markup import escape
|
|
49
50
|
from rich.table import Table
|
|
@@ -233,7 +234,35 @@ def _summarize_item(item: dict[str, Any]) -> str:
|
|
|
233
234
|
return " ".join(parts)
|
|
234
235
|
|
|
235
236
|
|
|
236
|
-
def
|
|
237
|
+
def _summarize_list(items: list[dict[str, Any]]) -> str:
|
|
238
|
+
"""Compact list-view summary of an object array: the count, plus a grouped
|
|
239
|
+
state breakdown when the items carry a state-like field (e.g. instances).
|
|
240
|
+
Full per-item detail is available via the matching ``show`` command."""
|
|
241
|
+
|
|
242
|
+
n = len(items)
|
|
243
|
+
key = next(
|
|
244
|
+
(
|
|
245
|
+
k
|
|
246
|
+
for k in ("state_last_known", "state", "status", "phase")
|
|
247
|
+
if any(k in it for it in items)
|
|
248
|
+
),
|
|
249
|
+
None,
|
|
250
|
+
)
|
|
251
|
+
if key is None:
|
|
252
|
+
return str(n)
|
|
253
|
+
counts: dict[str, int] = {}
|
|
254
|
+
for item in items:
|
|
255
|
+
value = str(item.get(key, ""))
|
|
256
|
+
counts[value] = counts.get(value, 0) + 1
|
|
257
|
+
if len(counts) == 1:
|
|
258
|
+
return f"{n} {next(iter(counts))}".strip()
|
|
259
|
+
return ", ".join(f"{count} {value}" for value, count in counts.items())
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _cell(value: Any, *, compact: bool = False) -> str:
|
|
263
|
+
"""Render a value for a table cell. ``compact`` (list views) collapses an
|
|
264
|
+
object array to a count summary; otherwise each item gets its own line."""
|
|
265
|
+
|
|
237
266
|
if value is None:
|
|
238
267
|
return ""
|
|
239
268
|
if isinstance(value, bool):
|
|
@@ -244,8 +273,10 @@ def _cell(value: Any) -> str:
|
|
|
244
273
|
if not value:
|
|
245
274
|
return ""
|
|
246
275
|
if all(isinstance(item, dict) for item in value):
|
|
247
|
-
# a list of objects (instances, mailboxes, ips, ...)
|
|
248
|
-
#
|
|
276
|
+
# a list of objects (instances, mailboxes, ips, ...): a count
|
|
277
|
+
# summary in lists, one line per item in detail views.
|
|
278
|
+
if compact:
|
|
279
|
+
return _summarize_list(value)
|
|
249
280
|
return "\n".join(_summarize_item(item) for item in value)
|
|
250
281
|
if not any(isinstance(item, (dict, list)) for item in value):
|
|
251
282
|
return ", ".join(str(item) for item in value)
|
|
@@ -272,15 +303,80 @@ def _flatten_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
|
272
303
|
return flat
|
|
273
304
|
|
|
274
305
|
|
|
306
|
+
def _is_object_list(value: Any) -> bool:
|
|
307
|
+
return (
|
|
308
|
+
isinstance(value, list)
|
|
309
|
+
and len(value) > 0
|
|
310
|
+
and all(isinstance(item, dict) for item in value)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _object_table(items: list[dict[str, Any]]) -> Table:
|
|
315
|
+
"""A compact sub-table for an array of objects, used inside the detail
|
|
316
|
+
view so nested data (instances, mailboxes, ips, ...) reads as a table
|
|
317
|
+
rather than one long line."""
|
|
318
|
+
|
|
319
|
+
cols = _auto_columns(items)
|
|
320
|
+
if not cols: # all columns dropped (e.g. identical rows): show everything
|
|
321
|
+
cols = list(dict.fromkeys(key for item in items for key in item))
|
|
322
|
+
table = Table(box=box.SIMPLE_HEAD, show_header=True, pad_edge=False)
|
|
323
|
+
for col in cols:
|
|
324
|
+
table.add_column(col)
|
|
325
|
+
for item in items:
|
|
326
|
+
table.add_row(*(_cell(item.get(col), compact=True) for col in cols))
|
|
327
|
+
return table
|
|
328
|
+
|
|
329
|
+
|
|
275
330
|
def _render_kv(data: dict[str, Any], title: str | None) -> None:
|
|
276
331
|
table = Table(title=title, show_header=False)
|
|
277
332
|
table.add_column(style="bold")
|
|
278
|
-
table.add_column()
|
|
333
|
+
table.add_column(overflow="fold")
|
|
279
334
|
for key, value in _flatten_row(data).items():
|
|
280
|
-
|
|
335
|
+
if value is None or value == "" or value == [] or value == {}:
|
|
336
|
+
continue # drop empty fields so the detail view stays readable
|
|
337
|
+
cell: Any = _object_table(value) if _is_object_list(value) else _cell(value)
|
|
338
|
+
table.add_row(key, cell)
|
|
281
339
|
out_console.print(table)
|
|
282
340
|
|
|
283
341
|
|
|
342
|
+
def _auto_columns(rows: list[dict[str, Any]]) -> list[str]:
|
|
343
|
+
"""Choose the columns worth showing in a list view, from the data itself.
|
|
344
|
+
|
|
345
|
+
Two things get dropped, because a list is an overview and the full record
|
|
346
|
+
is always available via the matching ``show`` command (or ``-o json``):
|
|
347
|
+
|
|
348
|
+
* columns empty in every row (no information at all);
|
|
349
|
+
* columns holding a list of objects (instances, mailboxes, ips, ...) -
|
|
350
|
+
these are detail, belonging in the ``show`` view, not the overview.
|
|
351
|
+
|
|
352
|
+
Every other field is kept, even if its value is the same on every row.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
cols: list[str] = []
|
|
356
|
+
for row in rows:
|
|
357
|
+
if isinstance(row, dict):
|
|
358
|
+
for key in row:
|
|
359
|
+
if key not in cols:
|
|
360
|
+
cols.append(key)
|
|
361
|
+
|
|
362
|
+
def empty(value: Any) -> bool:
|
|
363
|
+
return value is None or value == "" or value == [] or value == {}
|
|
364
|
+
|
|
365
|
+
def is_object_list(col: str) -> bool:
|
|
366
|
+
return any(
|
|
367
|
+
isinstance(row.get(col), list)
|
|
368
|
+
and row.get(col)
|
|
369
|
+
and all(isinstance(item, dict) for item in row[col])
|
|
370
|
+
for row in rows
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return [
|
|
374
|
+
c
|
|
375
|
+
for c in cols
|
|
376
|
+
if any(not empty(row.get(c)) for row in rows) and not is_object_list(c)
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
|
|
284
380
|
def _render_rows(
|
|
285
381
|
rows: list[dict[str, Any]], columns: list[str] | None, title: str | None
|
|
286
382
|
) -> None:
|
|
@@ -288,12 +384,12 @@ def _render_rows(
|
|
|
288
384
|
err_console.print("[dim]no results[/]")
|
|
289
385
|
return
|
|
290
386
|
rows = [_flatten_row(row) if isinstance(row, dict) else row for row in rows]
|
|
291
|
-
cols = columns
|
|
387
|
+
cols = columns if columns is not None else _auto_columns(rows)
|
|
292
388
|
table = Table(title=title)
|
|
293
389
|
for col in cols:
|
|
294
390
|
table.add_column(col)
|
|
295
391
|
for row in rows:
|
|
296
|
-
table.add_row(*(_cell(row.get(col)) for col in cols))
|
|
392
|
+
table.add_row(*(_cell(row.get(col), compact=True) for col in cols))
|
|
297
393
|
out_console.print(table)
|
|
298
394
|
|
|
299
395
|
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
dncli.py,sha256=h6cfEBaxc5RrRRmVnD3OiOCD7GMJxAfez0iY7gHNEoQ,120626
|
|
2
|
-
devnomads_cli-0.5.2.dist-info/licenses/LICENSE,sha256=WPXQ_lmpr3tVzTD9Z6VTiBgFnFey-cEwEYoG4HlmbxY,1066
|
|
3
|
-
devnomads_cli-0.5.2.dist-info/METADATA,sha256=OzLplEX2KBKUnnUuzPDrzyyTV71kZaLF10G1U0YjLzc,5268
|
|
4
|
-
devnomads_cli-0.5.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
-
devnomads_cli-0.5.2.dist-info/entry_points.txt,sha256=irLaKiGtIzEKi87exmXAg0srUmoDK31fL0SlqJasAEU,69
|
|
6
|
-
devnomads_cli-0.5.2.dist-info/top_level.txt,sha256=rFWIJDQgL3jA2q6b2GAqZgCknDED4WLxNcHTktuWS1I,6
|
|
7
|
-
devnomads_cli-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|