123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- from __future__ import annotations
- import os
- import re
- 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 enabled by default on continuous integration.
- IS_CI: Final[bool] = bool(os.environ.get("CI"))
- NO_COLOR: Final[bool] = bool(os.environ.get("NO_COLOR"))
- CLICOLOR_FORCE: Final[bool] = bool(os.environ.get("CLICOLOR_FORCE"))
- STDOUT_TTY: Final[bool] = bool(sys.stdout.isatty())
- STDERR_TTY: Final[bool] = bool(sys.stderr.isatty())
- _STDOUT_ORIGINAL: Final[bool] = False if NO_COLOR else CLICOLOR_FORCE or IS_CI or STDOUT_TTY
- _STDERR_ORIGINAL: Final[bool] = False if NO_COLOR else CLICOLOR_FORCE or IS_CI or STDERR_TTY
- _stdout_override: bool = _STDOUT_ORIGINAL
- _stderr_override: bool = _STDERR_ORIGINAL
- def is_stdout_color() -> bool:
- return _stdout_override
- def is_stderr_color() -> bool:
- return _stderr_override
- def force_stdout_color(value: bool) -> None:
- """
- Explicitly set `stdout` support for ANSI escape codes.
- If environment overrides exist, does nothing.
- """
- if not NO_COLOR or not CLICOLOR_FORCE:
- global _stdout_override
- _stdout_override = value
- def force_stderr_color(value: bool) -> None:
- """
- Explicitly set `stderr` support for ANSI escape codes.
- If environment overrides exist, does nothing.
- """
- if not NO_COLOR or not CLICOLOR_FORCE:
- global _stderr_override
- _stderr_override = value
- 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
- RE_ANSI = re.compile(r"\x1b\[[=\?]?[;\d]+[a-zA-Z]")
- def color_print(*values: object, sep: str | None = " ", end: str | None = "\n", flush: bool = False) -> None:
- """Prints a colored message to `stdout`. If disabled, ANSI codes are automatically stripped."""
- if is_stdout_color():
- print(*values, sep=sep, end=f"{Ansi.RESET}{end}", flush=flush)
- else:
- print(RE_ANSI.sub("", (sep or " ").join(map(str, values))), sep="", end=end, flush=flush)
- def color_printerr(*values: object, sep: str | None = " ", end: str | None = "\n", flush: bool = False) -> None:
- """Prints a colored message to `stderr`. If disabled, ANSI codes are automatically stripped."""
- if is_stderr_color():
- print(*values, sep=sep, end=f"{Ansi.RESET}{end}", flush=flush, file=sys.stderr)
- else:
- print(RE_ANSI.sub("", (sep or " ").join(map(str, values))), sep="", end=end, flush=flush, file=sys.stderr)
- def print_info(*values: object) -> None:
- """Prints a informational message with formatting."""
- color_print(f"{Ansi.GRAY}{Ansi.BOLD}INFO:{Ansi.REGULAR}", *values)
- def print_warning(*values: object) -> None:
- """Prints a warning message with formatting."""
- color_printerr(f"{Ansi.YELLOW}{Ansi.BOLD}WARNING:{Ansi.REGULAR}", *values)
- def print_error(*values: object) -> None:
- """Prints an error message with formatting."""
- color_printerr(f"{Ansi.RED}{Ansi.BOLD}ERROR:{Ansi.REGULAR}", *values)
- if sys.platform == "win32":
- def _win_color_fix():
- """Attempts to enable ANSI escape code support on Windows 10 and later."""
- from ctypes import POINTER, WINFUNCTYPE, WinError, windll
- from ctypes.wintypes import BOOL, DWORD, HANDLE
- STDOUT_HANDLE = -11
- STDERR_HANDLE = -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
- for handle_id in [STDOUT_HANDLE, STDERR_HANDLE]:
- try:
- handle = GetStdHandle(handle_id)
- flags = GetConsoleMode(handle)
- SetConsoleMode(handle, flags | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
- except OSError:
- pass
- _win_color_fix()
|