"""Format coloured console output."""
from __future__ import annotations
import sys
from os import environ as _environ
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable
if sys.platform == 'win32':
import colorama
colorama.just_fix_windows_console()
del colorama
_COLOURING_DISABLED = False
def terminal_supports_colour() -> bool:
"""Return True if coloured terminal output is supported."""
if 'NO_COLOUR' in _environ or 'NO_COLOR' in _environ:
return False
if sys.platform == 'win32':
return True
if 'FORCE_COLOUR' in _environ or 'FORCE_COLOR' in _environ:
return True
if _environ.get('CI', '').lower() in {'true', '1'}:
return True
try:
if not sys.stdout.isatty():
return False
except (AttributeError, ValueError):
# Handle cases where .isatty() is not defined, or where e.g.
# "ValueError: I/O operation on closed file" is raised
return False
# Do not colour output if on a dumb terminal
return _environ.get('TERM', 'unknown').lower() not in {'dumb', 'unknown'}
def disable_colour() -> None:
global _COLOURING_DISABLED # NoQA: PLW0603
_COLOURING_DISABLED = True
def enable_colour() -> None:
global _COLOURING_DISABLED # NoQA: PLW0603
_COLOURING_DISABLED = False
def colourise(colour_name: str, text: str, /) -> str:
if _COLOURING_DISABLED:
return text
if colour_name.startswith('_') or colour_name in {
'annotations',
'sys',
'terminal_supports_colour',
'disable_colour',
'enable_colour',
'colourise',
}:
msg = f'Invalid colour name: {colour_name!r}'
raise ValueError(msg)
try:
return globals()[colour_name](text)
except KeyError:
msg = f'Invalid colour name: {colour_name!r}'
raise ValueError(msg) from None
def _create_colour_func(escape_code: str, /) -> Callable[[str], str]:
def inner(text: str) -> str:
if _COLOURING_DISABLED:
return text
return f'\x1b[{escape_code}m{text}\x1b[39;49;00m'
inner.__escape_code = escape_code # type: ignore[attr-defined]
return inner
# Wrap escape sequence with ``\1`` and ``\2`` to let readline know
# that the colour escape codes are non-printable characters
# [ https://tiswww.case.edu/php/chet/readline/readline.html ]
#
# Note: This does not work well in Windows
# (see https://github.com/sphinx-doc/sphinx/pull/5059)
if sys.platform == 'win32':
_create_input_mode_colour_func = _create_colour_func
else:
def _create_input_mode_colour_func(escape_code: str, /) -> Callable[[str], str]:
def inner(text: str) -> str:
if _COLOURING_DISABLED:
return text
return f'\1\x1b[{escape_code}m\2{text}\1\x1b[39;49;00m\2'
return inner
reset = _create_colour_func('39;49;00')
bold = _create_colour_func('01')
faint = _create_colour_func('02')
standout = _create_colour_func('03')
underline = _create_colour_func('04')
blink = _create_colour_func('05')
black = _create_colour_func('30')
darkred = _create_colour_func('31')
darkgreen = _create_colour_func('32')
brown = _create_colour_func('33')
darkblue = _create_colour_func('34')
purple = _create_colour_func('35')
turquoise = _create_colour_func('36')
lightgray = _create_colour_func('37')
darkgray = _create_colour_func('90')
red = _create_colour_func('91')
green = _create_colour_func('92')
yellow = _create_colour_func('93')
blue = _create_colour_func('94')
fuchsia = _create_colour_func('95')
teal = _create_colour_func('96')
white = _create_colour_func('97')