Files
flow/core/console.py
2026-02-12 09:42:59 +02:00

139 lines
5.1 KiB
Python

"""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)