flow
This commit is contained in:
138
core/console.py
Normal file
138
core/console.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user