2025-09-12 22:25:05 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LogLevel(Enum):
|
|
|
|
|
|
"""Log levels with associated colors and symbols."""
|
|
|
|
|
|
|
|
|
|
|
|
DEBUG = ("🔍", "\033[90m") # Gray
|
|
|
|
|
|
INFO = ("ℹ️ ", "\033[94m") # Blue
|
|
|
|
|
|
SUCCESS = ("✅", "\033[92m") # Green
|
|
|
|
|
|
WARNING = ("⚠️ ", "\033[93m") # Yellow
|
|
|
|
|
|
ERROR = ("❌", "\033[91m") # Red
|
|
|
|
|
|
STEP = ("🚀", "\033[96m") # Cyan
|
|
|
|
|
|
PROGRESS = ("📋", "\033[95m") # Magenta
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Logger:
|
|
|
|
|
|
"""Logger class for formatted console output."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, name: str | None = None, use_colors: bool = True):
|
|
|
|
|
|
"""Initialize logger.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
name: Optional name for the logger (e.g., script name)
|
|
|
|
|
|
use_colors: Whether to use ANSI color codes
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.name = name
|
|
|
|
|
|
self.use_colors = use_colors and sys.stdout.isatty()
|
|
|
|
|
|
self._reset_color = "\033[0m" if self.use_colors else ""
|
|
|
|
|
|
|
|
|
|
|
|
def _format_message(self, level: LogLevel, message: str, indent: int = 0) -> str:
|
|
|
|
|
|
"""Format a log message with level, color, and indentation.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
level: Log level
|
|
|
|
|
|
message: Message to log
|
|
|
|
|
|
indent: Number of spaces to indent
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Formatted message string
|
|
|
|
|
|
"""
|
|
|
|
|
|
symbol, color = level.value
|
|
|
|
|
|
color = color if self.use_colors else ""
|
|
|
|
|
|
reset = self._reset_color
|
|
|
|
|
|
|
|
|
|
|
|
prefix = " " * indent
|
|
|
|
|
|
|
|
|
|
|
|
if self.name and level in [LogLevel.STEP, LogLevel.ERROR]:
|
|
|
|
|
|
return f"{prefix}{color}{symbol} [{self.name}] {message}{reset}"
|
|
|
|
|
|
else:
|
|
|
|
|
|
return f"{prefix}{color}{symbol} {message}{reset}"
|
|
|
|
|
|
|
|
|
|
|
|
def debug(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log debug message."""
|
|
|
|
|
|
print(self._format_message(LogLevel.DEBUG, message, indent))
|
|
|
|
|
|
|
|
|
|
|
|
def info(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log info message."""
|
|
|
|
|
|
print(self._format_message(LogLevel.INFO, message, indent))
|
|
|
|
|
|
|
|
|
|
|
|
def success(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log success message."""
|
|
|
|
|
|
print(self._format_message(LogLevel.SUCCESS, message, indent))
|
|
|
|
|
|
|
|
|
|
|
|
def warning(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log warning message."""
|
|
|
|
|
|
print(self._format_message(LogLevel.WARNING, message, indent))
|
|
|
|
|
|
|
|
|
|
|
|
def error(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log error message."""
|
|
|
|
|
|
print(self._format_message(LogLevel.ERROR, message, indent), file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
def step(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log a step in a process."""
|
|
|
|
|
|
print(self._format_message(LogLevel.STEP, message, indent))
|
|
|
|
|
|
|
|
|
|
|
|
def progress(self, message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log progress information."""
|
|
|
|
|
|
print(self._format_message(LogLevel.PROGRESS, message, indent))
|
|
|
|
|
|
|
|
|
|
|
|
def separator(self, char: str = "-", length: int = 60) -> None:
|
|
|
|
|
|
"""Print a separator line."""
|
|
|
|
|
|
print(char * length)
|
|
|
|
|
|
|
|
|
|
|
|
def header(self, title: str, width: int = 60) -> None:
|
|
|
|
|
|
"""Print a formatted header."""
|
|
|
|
|
|
if self.use_colors:
|
|
|
|
|
|
print(f"\n\033[1m{'=' * width}\033[0m") # Bold
|
|
|
|
|
|
print(f"\033[1m{title.center(width)}\033[0m")
|
|
|
|
|
|
print(f"\033[1m{'=' * width}\033[0m\n")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"\n{'=' * width}")
|
|
|
|
|
|
print(title.center(width))
|
|
|
|
|
|
print(f"{'=' * width}\n")
|
|
|
|
|
|
|
|
|
|
|
|
def box(self, title: str, width: int = 60) -> None:
|
|
|
|
|
|
"""Print a title in a box."""
|
|
|
|
|
|
border = "═" * (width - 2)
|
|
|
|
|
|
if self.use_colors:
|
|
|
|
|
|
print(f"\033[1m╔{border}╗\033[0m")
|
|
|
|
|
|
print(f"\033[1m║{title.center(width - 2)}║\033[0m")
|
|
|
|
|
|
print(f"\033[1m╚{border}╝\033[0m")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"╔{border}╗")
|
|
|
|
|
|
print(f"║{title.center(width - 2)}║")
|
|
|
|
|
|
print(f"╚{border}╝")
|
|
|
|
|
|
|
|
|
|
|
|
def list_item(self, item: str, indent: int = 2) -> None:
|
|
|
|
|
|
"""Print a list item."""
|
|
|
|
|
|
prefix = " " * indent
|
|
|
|
|
|
print(f"{prefix}• {item}")
|
|
|
|
|
|
|
|
|
|
|
|
def key_value(self, key: str, value: str, indent: int = 2) -> None:
|
|
|
|
|
|
"""Print a key-value pair."""
|
|
|
|
|
|
prefix = " " * indent
|
|
|
|
|
|
if self.use_colors:
|
|
|
|
|
|
print(f"{prefix}\033[1m{key}:\033[0m {value}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"{prefix}{key}: {value}")
|
|
|
|
|
|
|
|
|
|
|
|
def spinner_start(self, message: str) -> None:
|
|
|
|
|
|
"""Start a spinner (simple implementation)."""
|
|
|
|
|
|
sys.stdout.write(f"\r{message}... ")
|
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
|
|
|
def spinner_stop(self, success: bool = True, message: str | None = None) -> None:
|
|
|
|
|
|
"""Stop the spinner and show result."""
|
|
|
|
|
|
if success:
|
|
|
|
|
|
symbol = "✅" if message else "Done"
|
|
|
|
|
|
sys.stdout.write(f"\r{symbol} {message or ''}\n")
|
|
|
|
|
|
else:
|
|
|
|
|
|
symbol = "❌" if message else "Failed"
|
|
|
|
|
|
sys.stdout.write(f"\r{symbol} {message or ''}\n")
|
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProgressLogger:
|
|
|
|
|
|
"""Logger for tracking progress through multiple steps."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, total_steps: int, logger: Logger | None = None):
|
|
|
|
|
|
"""Initialize progress logger.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
total_steps: Total number of steps
|
|
|
|
|
|
logger: Logger instance to use (creates new if None)
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.total_steps = total_steps
|
|
|
|
|
|
self.current_step = 0
|
|
|
|
|
|
self.logger = logger or Logger()
|
|
|
|
|
|
self.start_time = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
def next_step(self, description: str) -> None:
|
|
|
|
|
|
"""Move to next step and log it."""
|
|
|
|
|
|
self.current_step += 1
|
|
|
|
|
|
elapsed = time.time() - self.start_time
|
|
|
|
|
|
|
|
|
|
|
|
if self.logger.use_colors:
|
|
|
|
|
|
progress_bar = self._create_progress_bar()
|
2025-09-16 13:58:12 +09:00
|
|
|
|
print(f"\n\033[1m[Step {self.current_step}/{self.total_steps}]\033[0m {progress_bar}")
|
2025-09-12 22:25:05 +08:00
|
|
|
|
self.logger.step(f"{description} (Elapsed: {elapsed:.1f}s)")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"\n[Step {self.current_step}/{self.total_steps}]")
|
|
|
|
|
|
self.logger.step(f"{description} (Elapsed: {elapsed:.1f}s)")
|
|
|
|
|
|
|
|
|
|
|
|
def _create_progress_bar(self, width: int = 20) -> str:
|
|
|
|
|
|
"""Create a simple progress bar."""
|
|
|
|
|
|
filled = int(width * self.current_step / self.total_steps)
|
|
|
|
|
|
bar = "█" * filled + "░" * (width - filled)
|
|
|
|
|
|
percentage = int(100 * self.current_step / self.total_steps)
|
|
|
|
|
|
return f"[{bar}] {percentage}%"
|
|
|
|
|
|
|
|
|
|
|
|
def complete(self) -> None:
|
|
|
|
|
|
"""Mark progress as complete."""
|
|
|
|
|
|
elapsed = time.time() - self.start_time
|
|
|
|
|
|
self.logger.success(f"All steps completed! Total time: {elapsed:.1f}s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create default logger instance
|
|
|
|
|
|
logger = Logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Convenience functions using default logger
|
|
|
|
|
|
def debug(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log debug message using default logger."""
|
|
|
|
|
|
logger.debug(message, indent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def info(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log info message using default logger."""
|
|
|
|
|
|
logger.info(message, indent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def success(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log success message using default logger."""
|
|
|
|
|
|
logger.success(message, indent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def warning(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log warning message using default logger."""
|
|
|
|
|
|
logger.warning(message, indent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def error(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log error message using default logger."""
|
|
|
|
|
|
logger.error(message, indent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def step(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log step using default logger."""
|
|
|
|
|
|
logger.step(message, indent)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def progress(message: str, indent: int = 0) -> None:
|
|
|
|
|
|
"""Log progress using default logger."""
|
|
|
|
|
|
logger.progress(message, indent)
|