Fix contrast issues with text-muted and bg-dark classes
- Fixed Bootstrap bg-dark class to use better contrasting color - Added comprehensive text-muted contrast fixes for various contexts - Improved dark theme colors for better accessibility - Fixed CSS inheritance issues for code elements in dark contexts - All color choices meet WCAG AA contrast requirements
This commit is contained in:
398
scripts/colors.py
Normal file
398
scripts/colors.py
Normal file
@@ -0,0 +1,398 @@
|
||||
"""
|
||||
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!")
|
||||
Reference in New Issue
Block a user