123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- from __future__ import annotations
- import os
- import sys
- from enum import Enum
- from typing import Final
- # Colors are disabled in non-TTY environments such as pipes. This means if output is redirected
- # to a file, it won't contain color codes. Colors are always enabled on continuous integration.
- IS_CI: Final[bool] = bool(os.environ.get("CI"))
- STDOUT_TTY: Final[bool] = bool(sys.stdout.isatty())
- STDERR_TTY: Final[bool] = bool(sys.stderr.isatty())
- def _color_supported(stdout: bool) -> bool:
- """
- Validates if the current environment supports colored output. Attempts to enable ANSI escape
- code support on Windows 10 and later.
- """
- if IS_CI:
- return True
- if sys.platform != "win32":
- return STDOUT_TTY if stdout else STDERR_TTY
- else:
- from ctypes import POINTER, WINFUNCTYPE, WinError, windll
- from ctypes.wintypes import BOOL, DWORD, HANDLE
- STD_HANDLE = -11 if stdout else -12
- ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
- def err_handler(result, func, args):
- if not result:
- raise WinError()
- return args
- GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32), ((1, "nStdHandle"),))
- GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(
- ("GetConsoleMode", windll.kernel32),
- ((1, "hConsoleHandle"), (2, "lpMode")),
- )
- GetConsoleMode.errcheck = err_handler
- SetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, DWORD)(
- ("SetConsoleMode", windll.kernel32),
- ((1, "hConsoleHandle"), (1, "dwMode")),
- )
- SetConsoleMode.errcheck = err_handler
- try:
- handle = GetStdHandle(STD_HANDLE)
- flags = GetConsoleMode(handle)
- SetConsoleMode(handle, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
- return True
- except OSError:
- return False
- STDOUT_COLOR: Final[bool] = _color_supported(True)
- STDERR_COLOR: Final[bool] = _color_supported(False)
- _stdout_override: bool = STDOUT_COLOR
- _stderr_override: bool = STDERR_COLOR
- def toggle_color(stdout: bool, value: bool | None = None) -> None:
- """
- Explicitly toggle color codes, regardless of support.
- - `stdout`: A boolean to choose the output stream. `True` for stdout, `False` for stderr.
- - `value`: An optional boolean to explicitly set the color state instead of toggling.
- """
- if stdout:
- global _stdout_override
- _stdout_override = value if value is not None else not _stdout_override
- else:
- global _stderr_override
- _stderr_override = value if value is not None else not _stderr_override
- class Ansi(Enum):
- """
- Enum class for adding ansi codepoints directly into strings. Automatically converts values to
- strings representing their internal value.
- """
- RESET = "\x1b[0m"
- BOLD = "\x1b[1m"
- DIM = "\x1b[2m"
- ITALIC = "\x1b[3m"
- UNDERLINE = "\x1b[4m"
- STRIKETHROUGH = "\x1b[9m"
- REGULAR = "\x1b[22;23;24;29m"
- BLACK = "\x1b[30m"
- RED = "\x1b[31m"
- GREEN = "\x1b[32m"
- YELLOW = "\x1b[33m"
- BLUE = "\x1b[34m"
- MAGENTA = "\x1b[35m"
- CYAN = "\x1b[36m"
- WHITE = "\x1b[37m"
- GRAY = "\x1b[90m"
- def __str__(self) -> str:
- return self.value
- def print_info(*values: object) -> None:
- """Prints a informational message with formatting."""
- if _stdout_override:
- print(f"{Ansi.GRAY}{Ansi.BOLD}INFO:{Ansi.REGULAR}", *values, Ansi.RESET)
- else:
- print("INFO:", *values)
- def print_warning(*values: object) -> None:
- """Prints a warning message with formatting."""
- if _stderr_override:
- print(f"{Ansi.YELLOW}{Ansi.BOLD}WARNING:{Ansi.REGULAR}", *values, Ansi.RESET, file=sys.stderr)
- else:
- print("WARNING:", *values, file=sys.stderr)
- def print_error(*values: object) -> None:
- """Prints an error message with formatting."""
- if _stderr_override:
- print(f"{Ansi.RED}{Ansi.BOLD}ERROR:{Ansi.REGULAR}", *values, Ansi.RESET, file=sys.stderr)
- else:
- print("ERROR:", *values, file=sys.stderr)
|