"""Console output formatting — ported from dotfiles_v2/src/console_logger.py.""" import time from typing import Optional class ConsoleLogger: # Color constants BLUE = "\033[34m" GREEN = "\033[32m" YELLOW = "\033[33m" RED = "\033[31m" CYAN = "\033[36m" GRAY = "\033[90m" DARK_GRAY = "\033[2;37m" BOLD = "\033[1m" DIM = "\033[2m" RESET = "\033[0m" # Box drawing characters BOX_VERTICAL = "\u2502" BOX_HORIZONTAL = "\u2500" BOX_TOP_LEFT = "\u250c" BOX_TOP_RIGHT = "\u2510" BOX_BOTTOM_LEFT = "\u2514" BOX_BOTTOM_RIGHT = "\u2518" def __init__(self): self.step_counter = 0 self.start_time = None def info(self, message: str): print(f"{self.CYAN}[INFO]{self.RESET} {message}") def warn(self, message: str): print(f"{self.YELLOW}[WARN]{self.RESET} {message}") def error(self, message: str): print(f"{self.RED}[ERROR]{self.RESET} {message}") def success(self, message: str): print(f"{self.GREEN}[SUCCESS]{self.RESET} {message}") def step_start(self, current: int, total: int, description: str): print( f"\n{self.BOLD}{self.BLUE}Step {current}/{total}:{self.RESET} " f"{self.BOLD}{description}{self.RESET}" ) print(f"{self.BLUE}{self.BOX_HORIZONTAL * 4}{self.RESET} {self.GRAY}Starting...{self.RESET}") self.start_time = time.time() def step_command(self, command: str): print(f"{self.BLUE}{self.BOX_VERTICAL} {self.RESET}{self.GRAY}$ {command}{self.RESET}") def step_output(self, line: str): if line.strip(): print(f"{self.BLUE}{self.BOX_VERTICAL} {self.RESET}{self.DARK_GRAY} {line.rstrip()}{self.RESET}") def step_complete(self, message: str = "Completed successfully"): elapsed = time.time() - self.start_time if self.start_time else 0 print(f"{self.BLUE}{self.BOX_VERTICAL} {self.RESET}{self.GREEN}> {message} ({elapsed:.1f}s){self.RESET}") def step_skip(self, message: str): elapsed = time.time() - self.start_time if self.start_time else 0 print( f"{self.BLUE}{self.BOX_VERTICAL} {self.RESET}" f"{self.YELLOW}> Skipped: {message} ({elapsed:.1f}s){self.RESET}" ) def step_fail(self, message: str): elapsed = time.time() - self.start_time if self.start_time else 0 print( f"{self.BLUE}{self.BOX_VERTICAL} {self.RESET}" f"{self.RED}> Failed: {message} ({elapsed:.1f}s){self.RESET}" ) def section_header(self, title: str, subtitle: str = ""): width = 70 print(f"\n{self.BOLD}{self.BLUE}{'=' * width}{self.RESET}") if subtitle: print(f"{self.BOLD}{self.BLUE} {title.upper()} - {subtitle}{self.RESET}") else: print(f"{self.BOLD}{self.BLUE} {title.upper()}{self.RESET}") print(f"{self.BOLD}{self.BLUE}{'=' * width}{self.RESET}") def section_summary(self, title: str): width = 70 print(f"\n{self.BOLD}{self.GREEN}{'=' * width}{self.RESET}") print(f"{self.BOLD}{self.GREEN} {title.upper()}{self.RESET}") print(f"{self.BOLD}{self.GREEN}{'=' * width}{self.RESET}") def plan_header(self, title: str, count: int): width = 70 print(f"\n{self.BOLD}{self.CYAN}{'=' * width}{self.RESET}") print(f"{self.BOLD}{self.CYAN} {title.upper()} ({count} actions){self.RESET}") print(f"{self.BOLD}{self.CYAN}{'=' * width}{self.RESET}") def plan_category(self, category: str): print(f"\n{self.BOLD}{self.CYAN}{category.upper()}{self.RESET}") print(f"{self.CYAN}{'-' * 20}{self.RESET}") def plan_item(self, number: int, description: str, os_filter: Optional[str] = None, critical: bool = False): os_indicator = f" {self.GRAY}({os_filter}){self.RESET}" if os_filter else "" error_indicator = f" {self.RED}(critical){self.RESET}" if critical else "" print(f" {number:2d}. {description}{os_indicator}{error_indicator}") def plan_legend(self): print( f"\n{self.GRAY}Legend: {self.RED}(critical){self.GRAY} = stops on failure, " f"{self.GRAY}(os){self.GRAY} = OS-specific{self.RESET}" ) def table(self, headers: list[str], rows: list[list[str]]): """Print a formatted table.""" if not rows: return normalized_headers = [str(h) for h in headers] normalized_rows = [[str(cell) for cell in row] for row in rows] # Calculate column widths widths = [len(h) for h in normalized_headers] for row in normalized_rows: for i, cell in enumerate(row): if i < len(widths): widths[i] = max(widths[i], len(cell)) # Header header_line = " ".join( f"{self.BOLD}{h:<{widths[i]}}{self.RESET}" for i, h in enumerate(normalized_headers) ) print(header_line) print(self.GRAY + " ".join("-" * w for w in widths) + self.RESET) # Rows for row in normalized_rows: line = " ".join(f"{cell:<{widths[i]}}" for i, cell in enumerate(row)) print(line)