mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-11 19:45:06 +00:00
refactor(console): style env listings like the rich-click help screens
This commit is contained in:
+17
-18
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user