""" UnitForge Color Utility for Python Centralized ANSI color definitions and utility functions for consistent terminal output """ import os import sys from typing import Optional class Colors: """ANSI color codes and formatting constants.""" # Basic colors RED = "\033[0;31m" GREEN = "\033[0;32m" YELLOW = "\033[1;33m" BLUE = "\033[0;34m" PURPLE = "\033[0;35m" CYAN = "\033[0;36m" WHITE = "\033[0;37m" GRAY = "\033[0;90m" # Bright colors BRIGHT_RED = "\033[1;31m" BRIGHT_GREEN = "\033[1;32m" BRIGHT_YELLOW = "\033[1;33m" BRIGHT_BLUE = "\033[1;34m" BRIGHT_PURPLE = "\033[1;35m" BRIGHT_CYAN = "\033[1;36m" BRIGHT_WHITE = "\033[1;37m" # Background colors BG_RED = "\033[41m" BG_GREEN = "\033[42m" BG_YELLOW = "\033[43m" BG_BLUE = "\033[44m" BG_PURPLE = "\033[45m" BG_CYAN = "\033[46m" BG_WHITE = "\033[47m" # Text formatting BOLD = "\033[1m" DIM = "\033[2m" ITALIC = "\033[3m" UNDERLINE = "\033[4m" BLINK = "\033[5m" REVERSE = "\033[7m" STRIKETHROUGH = "\033[9m" # Reset NC = "\033[0m" # No Color RESET = "\033[0m" # Status symbols INFO_SYMBOL = "ℹ" SUCCESS_SYMBOL = "✓" WARNING_SYMBOL = "⚠" ERROR_SYMBOL = "✗" DEBUG_SYMBOL = "🐛" def supports_color() -> bool: """ Check if the terminal supports colors. Returns: bool: True if colors are supported, False otherwise """ # Check if we're in a terminal if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty(): return False # Check environment variables if os.environ.get("NO_COLOR"): return False if os.environ.get("FORCE_COLOR"): return True # Check TERM environment variable term = os.environ.get("TERM", "") if term == "dumb": return False return True # Initialize colors based on support if supports_color(): c = Colors() else: # Create a class with empty strings for all colors class NoColors: def __getattr__(self, name): return "" c = NoColors() def color_text(text: str, color: str) -> str: """ Apply color to text. Args: text: The text to colorize color: The ANSI color code Returns: str: Colored text with reset at the end """ if not supports_color(): return text return f"{color}{text}{c.NC}" def red(text: str) -> str: """Return red colored text.""" return color_text(text, c.RED) def green(text: str) -> str: """Return green colored text.""" return color_text(text, c.GREEN) def yellow(text: str) -> str: """Return yellow colored text.""" return color_text(text, c.YELLOW) def blue(text: str) -> str: """Return blue colored text.""" return color_text(text, c.BLUE) def purple(text: str) -> str: """Return purple colored text.""" return color_text(text, c.PURPLE) def cyan(text: str) -> str: """Return cyan colored text.""" return color_text(text, c.CYAN) def white(text: str) -> str: """Return white colored text.""" return color_text(text, c.WHITE) def gray(text: str) -> str: """Return gray colored text.""" return color_text(text, c.GRAY) def bright_red(text: str) -> str: """Return bright red colored text.""" return color_text(text, c.BRIGHT_RED) def bright_green(text: str) -> str: """Return bright green colored text.""" return color_text(text, c.BRIGHT_GREEN) def bright_yellow(text: str) -> str: """Return bright yellow colored text.""" return color_text(text, c.BRIGHT_YELLOW) def bright_blue(text: str) -> str: """Return bright blue colored text.""" return color_text(text, c.BRIGHT_BLUE) def bright_purple(text: str) -> str: """Return bright purple colored text.""" return color_text(text, c.BRIGHT_PURPLE) def bright_cyan(text: str) -> str: """Return bright cyan colored text.""" return color_text(text, c.BRIGHT_CYAN) def bright_white(text: str) -> str: """Return bright white colored text.""" return color_text(text, c.BRIGHT_WHITE) def bold(text: str) -> str: """Return bold text.""" return color_text(text, c.BOLD) def dim(text: str) -> str: """Return dim text.""" return color_text(text, c.DIM) def italic(text: str) -> str: """Return italic text.""" return color_text(text, c.ITALIC) def underline(text: str) -> str: """Return underlined text.""" return color_text(text, c.UNDERLINE) def info(message: str, file=None) -> None: """Print an info message.""" output = f"{c.BLUE}{c.INFO_SYMBOL}{c.NC} {message}" print(output, file=file) def success(message: str, file=None) -> None: """Print a success message.""" output = f"{c.GREEN}{c.SUCCESS_SYMBOL}{c.NC} {message}" print(output, file=file) def warning(message: str, file=None) -> None: """Print a warning message.""" output = f"{c.YELLOW}{c.WARNING_SYMBOL}{c.NC} {message}" print(output, file=file) def error(message: str, file=None) -> None: """Print an error message.""" output = f"{c.RED}{c.ERROR_SYMBOL}{c.NC} {message}" print(output, file=file or sys.stderr) def debug(message: str, file=None) -> None: """Print a debug message (only if DEBUG environment variable is set).""" if os.environ.get("DEBUG"): output = f"{c.GRAY}{c.DEBUG_SYMBOL}{c.NC} {message}" print(output, file=file or sys.stderr) def header(text: str, file=None) -> None: """Print a header with underline.""" print(file=file) print(f"{c.BOLD}{c.BLUE}{text}{c.NC}", file=file) print(f"{c.BLUE}{'=' * len(text)}{c.NC}", file=file) def subheader(text: str, file=None) -> None: """Print a subheader with underline.""" print(file=file) print(f"{c.BOLD}{text}{c.NC}", file=file) print("-" * len(text), file=file) def step(current: int, total: int, description: str, file=None) -> None: """Print a step indicator.""" output = f"{c.CYAN}[{current}/{total}]{c.NC} {c.BOLD}{description}{c.NC}" print(output, file=file) def status(status_type: str, message: str, file=None) -> None: """ Print a status message with appropriate formatting. Args: status_type: Type of status (ok, fail, warn, info, skip) message: The status message file: Output file (defaults to stdout, stderr for errors) """ status_map = { "ok": (f"{c.GREEN}[ OK ]{c.NC}", None), "success": (f"{c.GREEN}[ OK ]{c.NC}", None), "done": (f"{c.GREEN}[ OK ]{c.NC}", None), "fail": (f"{c.RED}[ FAIL ]{c.NC}", sys.stderr), "error": (f"{c.RED}[ FAIL ]{c.NC}", sys.stderr), "failed": (f"{c.RED}[ FAIL ]{c.NC}", sys.stderr), "warn": (f"{c.YELLOW}[ WARN ]{c.NC}", None), "warning": (f"{c.YELLOW}[ WARN ]{c.NC}", None), "info": (f"{c.BLUE}[ INFO ]{c.NC}", None), "skip": (f"{c.GRAY}[ SKIP ]{c.NC}", None), "skipped": (f"{c.GRAY}[ SKIP ]{c.NC}", None), } status_prefix, default_file = status_map.get( status_type.lower(), (f"{c.WHITE}[ ]{c.NC}", None) ) output_file = file or default_file print(f"{status_prefix} {message}", file=output_file) def box_header(text: str, width: int = 50, file=None) -> None: """Print a fancy box header.""" padding = (width - len(text) - 2) // 2 print(f"{c.BLUE}╔{'═' * (width - 2)}╗{c.NC}", file=file) print( f"{c.BLUE}║{c.NC}{' ' * padding}{c.BOLD}{text}{c.NC}" f"{' ' * padding}{c.BLUE}║{c.NC}", file=file, ) print(f"{c.BLUE}╚{'═' * (width - 2)}╝{c.NC}", file=file) def box_message( text: str, color: Optional[str] = None, width: int = 50, file=None ) -> None: """Print a message in a box.""" box_color = color or c.BLUE padding = (width - len(text) - 2) // 2 print(f"{box_color}┌{'─' * (width - 2)}┐{c.NC}", file=file) print( f"{box_color}│{c.NC}{' ' * padding}{text}{' ' * padding}{box_color}│{c.NC}", file=file, ) print(f"{box_color}└{'─' * (width - 2)}┘{c.NC}", file=file) def progress_bar(current: int, total: int, width: int = 40, file=None) -> None: """Print a progress bar.""" percent = current * 100 // total filled = current * width // total empty = width - filled bar = f"{c.BLUE}[{'█' * filled}{'░' * empty}] {percent}% ({current}/{total}){c.NC}" print(f"\r{bar}", end="", file=file, flush=True) if current == total: print(file=file) # New line when complete class ColorizedFormatter: """A context manager for colorized output.""" def __init__(self, color: str): self.color = color def __enter__(self): if supports_color(): print(self.color, end="") return self def __exit__(self, exc_type, exc_val, exc_tb): if supports_color(): print(c.NC, end="") # Example usage and testing if __name__ == "__main__": print("\nUnitForge Color Utility Test") print("=" * 30) # Test basic colors print("\nBasic Colors:") print(red("Red text")) print(green("Green text")) print(yellow("Yellow text")) print(blue("Blue text")) print(purple("Purple text")) print(cyan("Cyan text")) # Test status functions print("\nStatus Messages:") info("This is an info message") success("This is a success message") warning("This is a warning message") error("This is an error message") debug("This is a debug message") # Test headers header("Main Header") subheader("Sub Header") # Test status indicators print("\nStatus Indicators:") status("ok", "Operation successful") status("fail", "Operation failed") status("warn", "Operation completed with warnings") status("info", "Information message") status("skip", "Operation skipped") # Test boxes print("\nBox Examples:") box_header("UnitForge Test", 30) box_message("Success!", c.GREEN, 30) # Test progress print("\nProgress Example:") import time for i in range(11): progress_bar(i, 10, 30) if i < 10: time.sleep(0.1) print("\nColor support:", "Yes" if supports_color() else "No") print("Test completed!")