refactor(console): style env listings like the rich-click help screens

This commit is contained in:
imSp4rky
2026-08-18 23:13:48 -06:00
parent ca543b3a14
commit 4aa217c206
3 changed files with 52 additions and 23 deletions
+17 -18
View File
@@ -7,12 +7,11 @@ from typing import Optional
import click
from rich.padding import Padding
from rich.table import Table
from rich.tree import Tree
from unshackle.core import binaries
from unshackle.core.config import POSSIBLE_CONFIG_PATHS, config, config_path
from unshackle.core.console import console
from unshackle.core.console import console, listing_table, print_wide
from unshackle.core.constants import context_settings
from unshackle.core.services import Services
from unshackle.core.temp import TASK_PREFIX, is_stale
@@ -132,14 +131,12 @@ def check() -> None:
total_required = 0
missing_required = []
table = Table(
title="Environment Dependencies", title_style="bold", show_header=True, header_style="bold", expand=False
)
table.add_column("Category", style="bold cyan", width=10)
table.add_column("Tool", width=16)
table = listing_table("Environment Dependencies", expand=True, show_lines=True)
table.add_column("Category", style="cyan", width=10)
table.add_column("Tool", style="text", width=16)
table.add_column("Status", justify="center", width=10)
table.add_column("Req", justify="center", width=4)
table.add_column("Purpose", style="bright_black", width=20)
table.add_column("Purpose", style="text2", overflow="fold")
last_cat = None
for dep in all_deps:
@@ -164,16 +161,16 @@ def check() -> None:
table.add_row(category, dep["name"], status, req, dep["desc"])
console.print(Padding(table, (1, 2)))
print_wide(table)
summary_parts = [f"[bold]Total:[/bold] {total_installed}/{len(all_deps)}"]
summary_parts = [f"[text]Total:[/text] [repr.number]{total_installed}[/]/[repr.number]{len(all_deps)}[/]"]
if all_required_installed:
summary_parts.append("[green]All required tools installed ✓[/green]")
else:
summary_parts.append(f"[red]Missing required: {', '.join(missing_required)}[/red]")
console.print(Padding(" ".join(summary_parts), (1, 2)))
console.print(Padding(" ".join(summary_parts), (0, 3, 1, 3)))
@env.command()
@@ -276,8 +273,8 @@ def theme() -> None:
pulse.append("━", style=lut[int(fade * 31)])
block = Group(header, swatch, Text(), docstring, *options, tracks, *logs, Text(), progress, pulse)
console.print(Padding(block, (1, 2, 1, 2)))
console.print(Rule(style=guide, characters="─"))
print_wide(block, (1, 2, 1, 2))
print_wide(Rule(style=guide, characters="─"), (0, 0))
console.print()
@@ -289,14 +286,16 @@ def info() -> None:
if config_path:
log.info(f"Config loaded from {config_path}")
else:
tree = Tree("No config file found, you can use any of the following locations:")
tree = Tree(
"[text]No config file found, you can use any of the following locations:[/]", guide_style="bright_black"
)
for i, path in enumerate(POSSIBLE_CONFIG_PATHS, start=1):
tree.add(f"[repr.number]{i}.[/] [text2]{path.resolve()}[/]")
console.print(Padding(tree, (0, 5)))
table = Table(title="Directories", title_style="bold", expand=True)
table.add_column("Name", no_wrap=True)
table.add_column("Path", no_wrap=False, overflow="fold")
table = listing_table("Directories", expand=True, show_lines=True)
table.add_column("Name", style="cyan", no_wrap=True)
table.add_column("Path", style="text2", no_wrap=False, overflow="fold")
path_vars = {
x: Path(os.getenv(x))
@@ -320,7 +319,7 @@ def info() -> None:
break
table.add_row(name.title(), str(path))
console.print(Padding(table, (1, 5)))
print_wide(table)
@env.group(name="clear", short_help="Clear an environment directory.", context_settings=context_settings)
+8 -5
View File
@@ -1,3 +1,4 @@
import logging
import re
import subprocess
from pathlib import Path
@@ -26,25 +27,27 @@ def refresh_services() -> None:
from unshackle.core.service_repo import is_repo_spec, refresh_repo
from unshackle.core.utils.redact import redact_path
log = logging.getLogger("util")
entries = config.directories.services
if not isinstance(entries, list):
entries = [entries]
specs = [e for e in entries if isinstance(e, str) and is_repo_spec(e)]
if not specs:
click.echo("No service repos configured in directories.services.")
log.info("No service repos configured in directories.services.")
return
# manual refresh force-overwrites local changes (hard reset to upstream), then reports the diff
for spec in specs:
dest, changes = refresh_repo(spec)
if not dest:
click.echo(f"Failed to update {spec} (see log).")
log.error(f"Failed to update {spec} (see log).")
continue
if changes:
click.echo(f"Updated {spec} → {redact_path(str(dest))}")
log.info(f"Updated {spec} → {redact_path(str(dest))}")
for line in changes:
click.echo(f" {line}")
log.info(f" {line}")
else:
click.echo(f"No changes {spec}")
log.info(f"No changes {spec}")
@util.command()
+27
View File
@@ -1,5 +1,6 @@
import logging
import math
import shutil
from datetime import datetime
from types import ModuleType
from typing import IO, Any, Callable, Iterable, List, Literal, Mapping, Optional, TextIO, Union
@@ -434,6 +435,30 @@ def listing_panel(renderable: RenderableType, title: str) -> Panel:
return Panel(renderable, title=title, box=box.SQUARE, border_style="bright_black")
def listing_table(title: Optional[str] = None, **kwargs: Any) -> Table:
"""Build a table in the style of the rich-click help tables, so listings and help screens match."""
kwargs.setdefault("box", box.HORIZONTALS)
kwargs.setdefault("show_edge", False) # no top or bottom rule, so the title runs straight into the header
kwargs.setdefault("border_style", "dark_gray")
kwargs.setdefault("header_style", "text")
kwargs.setdefault("pad_edge", False)
return Table(title=title, title_style="rule.text", title_justify="left", **kwargs)
def print_wide(renderable: RenderableType, pad: PaddingDimensions = (1, 3)) -> None:
"""Print a listing at the terminal width, indented like the rich-click help panels.
The shared console holds 80 columns to keep logs and progress compact. Long values such as paths
and URLs fold badly at that width, so this widens the console for one print and restores it after.
"""
fixed_width = console.width
console.width = max(fixed_width, shutil.get_terminal_size().columns)
try:
console.print(Padding(renderable, pad))
finally:
console.width = fixed_width
def prompt_user(prompt: str) -> str:
"""Ask the user for input on the shared console, themed and indented like the rest of the output."""
indent = " " * 5
@@ -450,5 +475,7 @@ __all__ = (
"SyncLive",
"console",
"listing_panel",
"listing_table",
"print_wide",
"prompt_user",
)