| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- from __future__ import annotations
- import logging
- from pathlib import Path
- from typing import TYPE_CHECKING
- from rich.console import Console
- from rich.table import Table
- from rich.tree import Tree
- if TYPE_CHECKING:
- from .template import Template
- logger = logging.getLogger(__name__)
- console = Console()
- class DisplayManager:
- """Handles all rich rendering for the CLI."""
- def display_templates_table(
- self, templates: list[dict], module_name: str, title: str
- ) -> None:
- """Display a table of templates."""
- 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(title=title)
- 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)
- for template_info in templates:
- template = template_info["template"]
- indent = template_info["indent"]
- name = template.metadata.name or "Unnamed Template"
- tags_list = template.metadata.tags or []
- tags = ", ".join(tags_list) if tags_list else "-"
- version = template.metadata.version or ""
- library = template.metadata.library or ""
- template_id = f"{indent}{template.id}"
- table.add_row(template_id, name, tags, version, library)
- console.print(table)
- def display_template_details(self, template: Template, template_id: str) -> None:
- """Display template information panel and variables table."""
- self._display_template_header(template, template_id)
- self._display_file_tree(template)
- self._display_variables_table(template)
- def display_section_header(self, title: str, description: str | None) -> None:
- """Display a section header."""
- console.print(f"\n[bold cyan]{title}[/bold cyan]")
- if description:
- console.print(f"[dim]{description}[/dim]")
- console.print("─" * 40, style="dim")
- def display_validation_error(self, message: str) -> None:
- """Display a validation error message."""
- console.print(f"[red]{message}[/red]")
- def _display_template_header(self, template: Template, template_id: str) -> None:
- """Display the header for a template."""
- template_name = template.metadata.name or "Unnamed Template"
- version = template.metadata.version or "Not specified"
- description = template.metadata.description or "No description available"
- console.print(
- f"[bold blue]{template_name} ({template_id} - [cyan]{version}[/cyan])[/bold blue]"
- )
- console.print(description)
- def _display_file_tree(self, template: Template) -> None:
- """Display the file structure of a template."""
- # Preserve the heading, then use the template id as the root directory label
- console.print()
- console.print("[bold blue]Template File Structure:[/bold blue]")
- # Use the template id as the root directory label (folder glyph + white name)
- file_tree = Tree(f"\uf07b [white]{template.id}[/white]")
- tree_nodes = {Path("."): file_tree}
- for template_file in sorted(
- template.template_files, key=lambda f: f.relative_path
- ):
- parts = template_file.relative_path.parts
- current_path = Path(".")
- current_node = file_tree
- for part in parts[:-1]:
- current_path = current_path / part
- if current_path not in tree_nodes:
- new_node = current_node.add(f"\uf07b [white]{part}[/white]")
- tree_nodes[current_path] = new_node
- current_node = new_node
- else:
- current_node = tree_nodes[current_path]
- # Determine display name (use output_path to detect final filename)
- display_name = template_file.output_path.name if hasattr(template_file, 'output_path') else template_file.relative_path.name
- # Default icons: use Nerd Font private-use-area codepoints (PUA).
- # Docker (Font Awesome) is typically U+F308. Default file U+F15B.
- docker_icon = "\uf308"
- default_file_icon = "\uf15b"
- j2_icon = "\ue235"
- if template_file.file_type == "j2":
- # Detect common docker compose filenames from the resulting output path
- lower_name = display_name.lower()
- compose_names = {"docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"}
- if lower_name in compose_names or lower_name.startswith("docker-compose") or "compose" in lower_name:
- icon = docker_icon
- else:
- icon = j2_icon
- current_node.add(f"[white]{icon} {display_name}[/white]")
- elif template_file.file_type == "static":
- current_node.add(f"[white]{default_file_icon} {display_name}[/white]")
- if file_tree.children:
- console.print(file_tree)
- def _display_variables_table(self, template: Template) -> None:
- """Display a table of variables for a template."""
- if not (template.variables and template.variables.has_sections()):
- return
- console.print()
- console.print("[bold blue]Template Variables:[/bold blue]")
- variables_table = Table(show_header=True, header_style="bold blue")
- variables_table.add_column("Variable", style="cyan", no_wrap=True)
- variables_table.add_column("Type", style="magenta")
- variables_table.add_column("Default", style="green")
- variables_table.add_column("Description", style="white")
- variables_table.add_column("Origin", style="yellow")
- first_section = True
- for section in template.variables.get_sections().values():
- if not section.variables:
- continue
- if not first_section:
- variables_table.add_row("", "", "", "", "", style="dim")
- first_section = False
- is_dimmed = False
- if section.toggle:
- toggle_var = section.variables.get(section.toggle)
- if toggle_var and not toggle_var.get_typed_value():
- is_dimmed = True
- disabled_text = " (disabled)" if is_dimmed else ""
- required_text = " [yellow](required)[/yellow]" if section.required else ""
- header_text = f"[bold dim]{section.title}{required_text}{disabled_text}[/bold dim]" if is_dimmed else f"[bold]{section.title}{required_text}{disabled_text}[/bold]"
- variables_table.add_row(header_text, "", "", "", "")
- for var_name, variable in section.variables.items():
- row_style = "dim" if is_dimmed else None
- default_val = str(variable.value) if variable.value is not None else ""
- if variable.sensitive:
- default_val = "********"
- elif len(default_val) > 30:
- default_val = default_val[:27] + "..."
- variables_table.add_row(
- f" {var_name}",
- variable.type or "str",
- default_val,
- variable.description or "",
- variable.origin or "unknown",
- style=row_style,
- )
- console.print(variables_table)
|