mirror of
https://github.com/ChristianLempa/boilerplates.git
synced 2026-08-28 02:24:12 -05:00
273 lines
9.7 KiB
Python
273 lines
9.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from rich.table import Table
|
|
from rich.tree import Tree
|
|
|
|
from .display_icons import IconManager
|
|
from .display_settings import DisplaySettings
|
|
|
|
if TYPE_CHECKING:
|
|
from .display_base import BaseDisplay
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TableDisplay:
|
|
"""Table rendering.
|
|
|
|
Provides methods for displaying data tables with flexible formatting.
|
|
"""
|
|
|
|
def __init__(self, settings: DisplaySettings, base: BaseDisplay):
|
|
"""Initialize TableDisplay.
|
|
|
|
Args:
|
|
settings: Display settings for formatting
|
|
base: BaseDisplay instance for utility methods
|
|
"""
|
|
self.settings = settings
|
|
self.base = base
|
|
|
|
def data_table(
|
|
self,
|
|
columns: list[dict],
|
|
rows: list,
|
|
title: str | None = None,
|
|
row_formatter: callable | None = None,
|
|
) -> None:
|
|
"""Display a data table with configurable columns and formatting.
|
|
|
|
Args:
|
|
columns: List of column definitions, each dict with:
|
|
- name: Column header text
|
|
- style: Optional Rich style (e.g., "bold", "cyan")
|
|
- no_wrap: Optional bool to prevent text wrapping
|
|
- justify: Optional justify ("left", "right", "center")
|
|
rows: List of data rows (dicts, tuples, or objects)
|
|
title: Optional table title
|
|
row_formatter: Optional function(row) -> tuple to transform row data
|
|
"""
|
|
table = Table(title=title, show_header=True)
|
|
|
|
# Add columns
|
|
for col in columns:
|
|
table.add_column(
|
|
col["name"],
|
|
style=col.get("style"),
|
|
no_wrap=col.get("no_wrap", False),
|
|
justify=col.get("justify", "left"),
|
|
)
|
|
|
|
# Add rows
|
|
for row in rows:
|
|
if row_formatter:
|
|
formatted_row = row_formatter(row)
|
|
elif isinstance(row, dict):
|
|
formatted_row = tuple(str(row.get(col["name"], "")) for col in columns)
|
|
else:
|
|
formatted_row = tuple(str(cell) for cell in row)
|
|
|
|
table.add_row(*formatted_row)
|
|
|
|
self.base._print_table(table)
|
|
|
|
def render_templates_table(self, templates: list, module_name: str, _title: str) -> None:
|
|
"""Display a table of templates with library type indicators.
|
|
|
|
Args:
|
|
templates: List of Template objects
|
|
module_name: Name of the module
|
|
_title: Title for the table (unused, kept for API compatibility)
|
|
"""
|
|
if not templates:
|
|
logger.info(f"No templates found for module '{module_name}'")
|
|
return
|
|
|
|
logger.info(f"Listing {len(templates)} templates for module '{module_name}'")
|
|
table = Table()
|
|
table.add_column("ID", style="bold", no_wrap=True)
|
|
table.add_column("Name")
|
|
table.add_column("Tags")
|
|
table.add_column("Version", no_wrap=True)
|
|
table.add_column("Library", no_wrap=True)
|
|
|
|
settings = self.settings
|
|
|
|
for template in templates:
|
|
name = template.metadata.name or settings.TEXT_UNNAMED_TEMPLATE
|
|
tags_list = template.metadata.tags or []
|
|
tags = ", ".join(tags_list) if tags_list else "-"
|
|
version = template.metadata.version.name if template.metadata.version else ""
|
|
|
|
# Format library with icon and color
|
|
library_name = template.metadata.library or ""
|
|
library_type = template.metadata.library_type or "git"
|
|
icon = IconManager.UI_LIBRARY_STATIC if library_type == "static" else IconManager.UI_LIBRARY_GIT
|
|
color = "yellow" if library_type == "static" else "blue"
|
|
library_display = f"[{color}]{icon} {library_name}[/{color}]"
|
|
|
|
table.add_row(template.id, name, tags, version, library_display)
|
|
|
|
self.base._print_table(table)
|
|
|
|
def render_status_table(
|
|
self,
|
|
_title: str,
|
|
rows: list[tuple[str, str, bool]],
|
|
columns: tuple[str, str] = ("Item", "Status"),
|
|
) -> None:
|
|
"""Display a status table with success/error indicators.
|
|
|
|
Args:
|
|
_title: Table title (unused, kept for API compatibility)
|
|
rows: List of tuples (name, message, success_bool)
|
|
columns: Column headers (name_header, status_header)
|
|
"""
|
|
table = Table(show_header=True)
|
|
table.add_column(columns[0], style="cyan", no_wrap=True)
|
|
table.add_column(columns[1])
|
|
|
|
for name, message, success in rows:
|
|
status_style = "green" if success else "red"
|
|
status_icon = IconManager.get_status_icon("success" if success else "error")
|
|
table.add_row(name, f"[{status_style}]{status_icon} {message}[/{status_style}]")
|
|
|
|
self.base._print_table(table)
|
|
|
|
def render_summary_table(self, title: str, items: dict[str, str]) -> None:
|
|
"""Display a simple two-column summary table.
|
|
|
|
Args:
|
|
title: Table title
|
|
items: Dictionary of key-value pairs to display
|
|
"""
|
|
settings = self.settings
|
|
table = Table(
|
|
title=title,
|
|
show_header=False,
|
|
box=None,
|
|
padding=settings.PADDING_TABLE_NORMAL,
|
|
)
|
|
table.add_column(style="bold")
|
|
table.add_column()
|
|
|
|
for key, value in items.items():
|
|
table.add_row(key, value)
|
|
|
|
self.base._print_table(table)
|
|
|
|
def render_file_operation_table(self, files: list[tuple[str, int, str]]) -> None:
|
|
"""Display a table of file operations with sizes and statuses.
|
|
|
|
Args:
|
|
files: List of tuples (file_path, size_bytes, status)
|
|
"""
|
|
settings = self.settings
|
|
table = Table(
|
|
show_header=True,
|
|
header_style=settings.STYLE_TABLE_HEADER,
|
|
box=None,
|
|
padding=settings.PADDING_TABLE_COMPACT,
|
|
)
|
|
table.add_column("File", style="white", no_wrap=False)
|
|
table.add_column("Size", justify="right", style=settings.COLOR_MUTED)
|
|
table.add_column("Status", style=settings.COLOR_WARNING)
|
|
|
|
for file_path, size_bytes, status in files:
|
|
size_str = self.base.format_file_size(size_bytes)
|
|
table.add_row(str(file_path), size_str, status)
|
|
|
|
self.base._print_table(table)
|
|
|
|
def _build_section_label(
|
|
self,
|
|
section_name: str,
|
|
section_data: dict,
|
|
show_all: bool,
|
|
) -> str:
|
|
"""Build section label with metadata."""
|
|
section_desc = section_data.get("description", "")
|
|
section_toggle = section_data.get("toggle")
|
|
section_needs = section_data.get("needs")
|
|
|
|
label = f"[cyan]{section_name}[/cyan]"
|
|
if section_toggle:
|
|
label += f" [dim](toggle: {section_toggle})[/dim]"
|
|
if section_needs:
|
|
needs_str = ", ".join(section_needs) if isinstance(section_needs, list) else section_needs
|
|
label += f" [dim](needs: {needs_str})[/dim]"
|
|
if show_all and section_desc:
|
|
label += f"\n [dim]{section_desc}[/dim]"
|
|
|
|
return label
|
|
|
|
def _build_variable_label(
|
|
self,
|
|
var_name: str,
|
|
var_data: dict,
|
|
show_all: bool,
|
|
) -> str:
|
|
"""Build variable label with type and default value."""
|
|
var_type = var_data.get("type", "string")
|
|
var_default = var_data.get("default", "")
|
|
var_desc = var_data.get("description", "")
|
|
var_is_secret = var_data.get("type") == "secret"
|
|
|
|
label = f"[green]{var_name}[/green] [dim]({var_type})[/dim]"
|
|
|
|
if var_default is not None and var_default != "":
|
|
settings = self.settings
|
|
display_val = settings.SENSITIVE_MASK if var_is_secret else str(var_default)
|
|
if not var_is_secret:
|
|
display_val = self.base.truncate(display_val, settings.VALUE_MAX_LENGTH_DEFAULT)
|
|
label += f" = [{settings.COLOR_WARNING}]{display_val}[/{settings.COLOR_WARNING}]"
|
|
|
|
if show_all and var_desc:
|
|
label += f"\n [dim]{var_desc}[/dim]"
|
|
|
|
return label
|
|
|
|
def _add_section_variables(self, section_node, section_vars: dict, show_all: bool) -> None:
|
|
"""Add variables to a section node."""
|
|
for var_name, var_data in section_vars.items():
|
|
if isinstance(var_data, dict):
|
|
var_label = self._build_variable_label(var_name, var_data, show_all)
|
|
section_node.add(var_label)
|
|
else:
|
|
# Simple key-value pair
|
|
section_node.add(f"[green]{var_name}[/green] = [yellow]{var_data}[/yellow]")
|
|
|
|
def render_config_tree(self, spec: dict, module_name: str, show_all: bool = False) -> None:
|
|
"""Display configuration spec as a tree view.
|
|
|
|
Args:
|
|
spec: The configuration spec dictionary
|
|
module_name: Name of the module
|
|
show_all: If True, show all details including descriptions
|
|
"""
|
|
if not spec:
|
|
self.base.text(f"No configuration found for module '{module_name}'", style="yellow")
|
|
return
|
|
|
|
# Create root tree node
|
|
icon = IconManager.config()
|
|
tree = Tree(f"[bold blue]{icon} {str.capitalize(module_name)} Configuration[/bold blue]")
|
|
|
|
for section_name, section_data in spec.items():
|
|
if not isinstance(section_data, dict):
|
|
continue
|
|
|
|
# Build and add section
|
|
section_label = self._build_section_label(section_name, section_data, show_all)
|
|
section_node = tree.add(section_label)
|
|
|
|
# Add variables to section
|
|
section_vars = section_data.get("vars") or {}
|
|
if section_vars:
|
|
self._add_section_variables(section_node, section_vars, show_all)
|
|
|
|
self.base._print_tree(tree)
|