from __future__ import annotations import io import os import sys COLORIZE = True # types if False: from typing import IO, Literal type ColorTag = Literal[ "PROMPT", "KEYWORD", "BUILTIN", "COMMENT", "STRING", "NUMBER", "OP", "DEFINITION", "SOFT_KEYWORD", "RESET", ] theme: dict[ColorTag, str] class ANSIColors: RESET = "\x1b[0m" BLACK = "\x1b[30m" BLUE = "\x1b[34m" CYAN = "\x1b[36m" GREEN = "\x1b[32m" GREY = "\x1b[90m" MAGENTA = "\x1b[35m" RED = "\x1b[31m" WHITE = "\x1b[37m" # more like LIGHT GRAY YELLOW = "\x1b[33m" BOLD = "\x1b[1m" BOLD_BLACK = "\x1b[1;30m" # DARK GRAY BOLD_BLUE = "\x1b[1;34m" BOLD_CYAN = "\x1b[1;36m" BOLD_GREEN = "\x1b[1;32m" BOLD_MAGENTA = "\x1b[1;35m" BOLD_RED = "\x1b[1;31m" BOLD_WHITE = "\x1b[1;37m" # actual WHITE BOLD_YELLOW = "\x1b[1;33m" # intense = like bold but without being bold INTENSE_BLACK = "\x1b[90m" INTENSE_BLUE = "\x1b[94m" INTENSE_CYAN = "\x1b[96m" INTENSE_GREEN = "\x1b[92m" INTENSE_MAGENTA = "\x1b[95m" INTENSE_RED = "\x1b[91m" INTENSE_WHITE = "\x1b[97m" INTENSE_YELLOW = "\x1b[93m" BACKGROUND_BLACK = "\x1b[40m" BACKGROUND_BLUE = "\x1b[44m" BACKGROUND_CYAN = "\x1b[46m" BACKGROUND_GREEN = "\x1b[42m" BACKGROUND_MAGENTA = "\x1b[45m" BACKGROUND_RED = "\x1b[41m" BACKGROUND_WHITE = "\x1b[47m" BACKGROUND_YELLOW = "\x1b[43m" INTENSE_BACKGROUND_BLACK = "\x1b[100m" INTENSE_BACKGROUND_BLUE = "\x1b[104m" INTENSE_BACKGROUND_CYAN = "\x1b[106m" INTENSE_BACKGROUND_GREEN = "\x1b[102m" INTENSE_BACKGROUND_MAGENTA = "\x1b[105m" INTENSE_BACKGROUND_RED = "\x1b[101m" INTENSE_BACKGROUND_WHITE = "\x1b[107m" INTENSE_BACKGROUND_YELLOW = "\x1b[103m" ColorCodes = set() NoColors = ANSIColors() for attr, code in ANSIColors.__dict__.items(): if not attr.startswith("__"): ColorCodes.add(code) setattr(NoColors, attr, "") def get_colors( colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None ) -> ANSIColors: if colorize or can_colorize(file=file): return ANSIColors() else: return NoColors def decolor(text: str) -> str: """Remove ANSI color codes from a string.""" for code in ColorCodes: text = text.replace(code, "") return text def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool: if file is None: file = sys.stdout if not sys.flags.ignore_environment: if os.environ.get("PYTHON_COLORS") == "0": return False if os.environ.get("PYTHON_COLORS") == "1": return True if os.environ.get("NO_COLOR"): return False if not COLORIZE: return False if os.environ.get("FORCE_COLOR"): return True if os.environ.get("TERM") == "dumb": return False if not hasattr(file, "fileno"): return False if sys.platform == "win32": try: import nt if not nt._supports_virtual_terminal(): return False except (ImportError, AttributeError): return False try: return os.isatty(file.fileno()) except io.UnsupportedOperation: return hasattr(file, "isatty") and file.isatty() def set_theme(t: dict[ColorTag, str] | None = None) -> None: global theme if t: theme = t return colors = get_colors() theme = { "PROMPT": colors.BOLD_MAGENTA, "KEYWORD": colors.BOLD_BLUE, "BUILTIN": colors.CYAN, "COMMENT": colors.RED, "STRING": colors.GREEN, "NUMBER": colors.YELLOW, "OP": colors.RESET, "DEFINITION": colors.BOLD, "SOFT_KEYWORD": colors.BOLD_BLUE, "RESET": colors.RESET, } set_theme()