mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +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
|
import click
|
||||||
from rich.padding import Padding
|
from rich.padding import Padding
|
||||||
from rich.table import Table
|
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
|
|
||||||
from unshackle.core import binaries
|
from unshackle.core import binaries
|
||||||
from unshackle.core.config import POSSIBLE_CONFIG_PATHS, config, config_path
|
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.constants import context_settings
|
||||||
from unshackle.core.services import Services
|
from unshackle.core.services import Services
|
||||||
from unshackle.core.temp import TASK_PREFIX, is_stale
|
from unshackle.core.temp import TASK_PREFIX, is_stale
|
||||||
@@ -132,14 +131,12 @@ def check() -> None:
|
|||||||
total_required = 0
|
total_required = 0
|
||||||
missing_required = []
|
missing_required = []
|
||||||
|
|
||||||
table = Table(
|
table = listing_table("Environment Dependencies", expand=True, show_lines=True)
|
||||||
title="Environment Dependencies", title_style="bold", show_header=True, header_style="bold", expand=False
|
table.add_column("Category", style="cyan", width=10)
|
||||||
)
|
table.add_column("Tool", style="text", width=16)
|
||||||
table.add_column("Category", style="bold cyan", width=10)
|
|
||||||
table.add_column("Tool", width=16)
|
|
||||||
table.add_column("Status", justify="center", width=10)
|
table.add_column("Status", justify="center", width=10)
|
||||||
table.add_column("Req", justify="center", width=4)
|
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
|
last_cat = None
|
||||||
for dep in all_deps:
|
for dep in all_deps:
|
||||||
@@ -164,16 +161,16 @@ def check() -> None:
|
|||||||
|
|
||||||
table.add_row(category, dep["name"], status, req, dep["desc"])
|
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:
|
if all_required_installed:
|
||||||
summary_parts.append("[green]All required tools installed ✓[/green]")
|
summary_parts.append("[green]All required tools installed ✓[/green]")
|
||||||
else:
|
else:
|
||||||
summary_parts.append(f"[red]Missing required: {', '.join(missing_required)}[/red]")
|
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()
|
@env.command()
|
||||||
@@ -276,8 +273,8 @@ def theme() -> None:
|
|||||||
pulse.append("━", style=lut[int(fade * 31)])
|
pulse.append("━", style=lut[int(fade * 31)])
|
||||||
|
|
||||||
block = Group(header, swatch, Text(), docstring, *options, tracks, *logs, Text(), progress, pulse)
|
block = Group(header, swatch, Text(), docstring, *options, tracks, *logs, Text(), progress, pulse)
|
||||||
console.print(Padding(block, (1, 2, 1, 2)))
|
print_wide(block, (1, 2, 1, 2))
|
||||||
console.print(Rule(style=guide, characters="─"))
|
print_wide(Rule(style=guide, characters="─"), (0, 0))
|
||||||
console.print()
|
console.print()
|
||||||
|
|
||||||
|
|
||||||
@@ -289,14 +286,16 @@ def info() -> None:
|
|||||||
if config_path:
|
if config_path:
|
||||||
log.info(f"Config loaded from {config_path}")
|
log.info(f"Config loaded from {config_path}")
|
||||||
else:
|
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):
|
for i, path in enumerate(POSSIBLE_CONFIG_PATHS, start=1):
|
||||||
tree.add(f"[repr.number]{i}.[/] [text2]{path.resolve()}[/]")
|
tree.add(f"[repr.number]{i}.[/] [text2]{path.resolve()}[/]")
|
||||||
console.print(Padding(tree, (0, 5)))
|
console.print(Padding(tree, (0, 5)))
|
||||||
|
|
||||||
table = Table(title="Directories", title_style="bold", expand=True)
|
table = listing_table("Directories", expand=True, show_lines=True)
|
||||||
table.add_column("Name", no_wrap=True)
|
table.add_column("Name", style="cyan", no_wrap=True)
|
||||||
table.add_column("Path", no_wrap=False, overflow="fold")
|
table.add_column("Path", style="text2", no_wrap=False, overflow="fold")
|
||||||
|
|
||||||
path_vars = {
|
path_vars = {
|
||||||
x: Path(os.getenv(x))
|
x: Path(os.getenv(x))
|
||||||
@@ -320,7 +319,7 @@ def info() -> None:
|
|||||||
break
|
break
|
||||||
table.add_row(name.title(), str(path))
|
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)
|
@env.group(name="clear", short_help="Clear an environment directory.", context_settings=context_settings)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
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.service_repo import is_repo_spec, refresh_repo
|
||||||
from unshackle.core.utils.redact import redact_path
|
from unshackle.core.utils.redact import redact_path
|
||||||
|
|
||||||
|
log = logging.getLogger("util")
|
||||||
|
|
||||||
entries = config.directories.services
|
entries = config.directories.services
|
||||||
if not isinstance(entries, list):
|
if not isinstance(entries, list):
|
||||||
entries = [entries]
|
entries = [entries]
|
||||||
specs = [e for e in entries if isinstance(e, str) and is_repo_spec(e)]
|
specs = [e for e in entries if isinstance(e, str) and is_repo_spec(e)]
|
||||||
if not specs:
|
if not specs:
|
||||||
click.echo("No service repos configured in directories.services.")
|
log.info("No service repos configured in directories.services.")
|
||||||
return
|
return
|
||||||
# manual refresh force-overwrites local changes (hard reset to upstream), then reports the diff
|
# manual refresh force-overwrites local changes (hard reset to upstream), then reports the diff
|
||||||
for spec in specs:
|
for spec in specs:
|
||||||
dest, changes = refresh_repo(spec)
|
dest, changes = refresh_repo(spec)
|
||||||
if not dest:
|
if not dest:
|
||||||
click.echo(f"Failed to update {spec} (see log).")
|
log.error(f"Failed to update {spec} (see log).")
|
||||||
continue
|
continue
|
||||||
if changes:
|
if changes:
|
||||||
click.echo(f"Updated {spec} → {redact_path(str(dest))}")
|
log.info(f"Updated {spec} → {redact_path(str(dest))}")
|
||||||
for line in changes:
|
for line in changes:
|
||||||
click.echo(f" {line}")
|
log.info(f" {line}")
|
||||||
else:
|
else:
|
||||||
click.echo(f"No changes {spec}")
|
log.info(f"No changes {spec}")
|
||||||
|
|
||||||
|
|
||||||
@util.command()
|
@util.command()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
import shutil
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import IO, Any, Callable, Iterable, List, Literal, Mapping, Optional, TextIO, Union
|
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")
|
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:
|
def prompt_user(prompt: str) -> str:
|
||||||
"""Ask the user for input on the shared console, themed and indented like the rest of the output."""
|
"""Ask the user for input on the shared console, themed and indented like the rest of the output."""
|
||||||
indent = " " * 5
|
indent = " " * 5
|
||||||
@@ -450,5 +475,7 @@ __all__ = (
|
|||||||
"SyncLive",
|
"SyncLive",
|
||||||
"console",
|
"console",
|
||||||
"listing_panel",
|
"listing_panel",
|
||||||
|
"listing_table",
|
||||||
|
"print_wide",
|
||||||
"prompt_user",
|
"prompt_user",
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user