feat: improve remote browser tree routing
Testing / remote-protocol-compat (0.9.3) (push) Successful in 43s
Testing / test (push) Successful in 1m1s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 39s
Build & Publish Package / publish (push) Successful in 58s
Package Extension / package-extension (push) Successful in 1m15s
Testing / remote-protocol-compat (0.9.3) (push) Successful in 43s
Testing / test (push) Successful in 1m1s
Testing / remote-protocol-compat (0.9.5) (push) Successful in 39s
Build & Publish Package / publish (push) Successful in 58s
Package Extension / package-extension (push) Successful in 1m15s
- Allow remote host aliases passed via --browser to fan out for read-only multi-browser SDK paths while preserving strict routing for mutating commands. - Add remote host grouping and scoped profile labels to tabs tree output so global views avoid repeated host prefixes. - Carry browser family metadata through remote targets, tabs, and groups and style tree browser labels by family. - Split CLI rendering helpers into a typed rendering package with dedicated common, label, tabs-tree, and windows-tree modules. - Bump browser-cli and extension versions to 0.15.5. - Cover the new routing and rendering behavior with unit and CLI tests.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Common Rich rendering helpers for CLI command modules."""
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
from typing import TypeVar, cast
|
||||
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.tree import Tree
|
||||
|
||||
Row = object
|
||||
CellValue = object
|
||||
Column = tuple[str, Callable[[Row], CellValue]]
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def item_value(item: Row, name: str, default: T | None = None) -> CellValue | T | None:
|
||||
"""Read *name* from a dict-like or attribute object."""
|
||||
if isinstance(item, Mapping):
|
||||
return cast(Mapping[str, CellValue], item).get(name, default)
|
||||
return getattr(item, name, default)
|
||||
|
||||
def text_value(value: CellValue | None, default: str = "") -> str:
|
||||
"""Coerce a nullable cell value to display text."""
|
||||
return default if value is None else str(value)
|
||||
|
||||
def int_value(value: CellValue | None, default: int = 0) -> int:
|
||||
"""Coerce a cell value to int, falling back when conversion is not possible."""
|
||||
try:
|
||||
return int(cast(int | str | float | bool, value))
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
def shorten(value: str | None, limit: int) -> str:
|
||||
"""Return *value* shortened to *limit* cells-ish, using an ellipsis."""
|
||||
value = value or ""
|
||||
return value if len(value) <= limit else value[:max(0, limit - 1)] + "…"
|
||||
|
||||
def terminal_width(console: Console | None = None, *, fallback: int = 120) -> int:
|
||||
"""Best-effort terminal width for interactive and redirected output.
|
||||
|
||||
Rich falls back to 80 columns when stdout is redirected. browser-cli output is
|
||||
often piped into files for inspection, so also consult ``shutil``/``COLUMNS``
|
||||
and prefer the wider value.
|
||||
"""
|
||||
rich_width = (console.width if console is not None else 0) or 0
|
||||
shell_width = shutil.get_terminal_size((fallback, 20)).columns
|
||||
return max(rich_width, shell_width)
|
||||
|
||||
def tree_title_limit(*, console: Console | None = None, show_browser: bool = False, show_urls: bool = False) -> int:
|
||||
"""Title width for tree labels, reserving space for branches/IDs/metadata."""
|
||||
reserve = 48 if show_urls else 32
|
||||
if show_browser:
|
||||
reserve += 4
|
||||
return max(50, terminal_width(console) - reserve)
|
||||
|
||||
def tree_url_limit(title_limit: int, *, console: Console | None = None) -> int:
|
||||
"""URL width for tree labels when URLs are displayed."""
|
||||
return max(35, terminal_width(console) - title_limit - 40)
|
||||
|
||||
def print_tree(tree: Tree, *, console: Console | None = None) -> None:
|
||||
"""Render a Rich tree using the detected full terminal width."""
|
||||
Console(width=terminal_width(console)).print(tree)
|
||||
|
||||
def print_table_rows(
|
||||
rows: Sequence[Row],
|
||||
columns: Sequence[Column],
|
||||
*,
|
||||
console: Console,
|
||||
empty_message: str,
|
||||
show_header: bool = True,
|
||||
header_style: str = "bold cyan",
|
||||
) -> None:
|
||||
"""Render a small Rich table from arbitrary row objects."""
|
||||
if not rows:
|
||||
console.print(empty_message)
|
||||
return
|
||||
table = Table(show_header=show_header, header_style=header_style)
|
||||
for header, _getter in columns:
|
||||
table.add_column(header)
|
||||
for row in rows:
|
||||
table.add_row(*[text_value(getter(row)) for _header, getter in columns])
|
||||
Console(width=terminal_width(console)).print(table)
|
||||
Reference in New Issue
Block a user