|
| 1 | +"""Format coloured console output.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import os |
| 6 | +import sys |
| 7 | +from typing import Callable |
| 8 | + |
| 9 | +if sys.platform == 'win32': |
| 10 | + import colorama |
| 11 | + |
| 12 | + |
| 13 | +_COLOURING_DISABLED = True |
| 14 | + |
| 15 | + |
| 16 | +def terminal_supports_colour() -> bool: |
| 17 | + """Return True if coloured terminal output is supported.""" |
| 18 | + if 'NO_COLOR' in os.environ: |
| 19 | + return False |
| 20 | + if 'FORCE_COLOR' in os.environ: |
| 21 | + return True |
| 22 | + |
| 23 | + try: |
| 24 | + if not sys.stdout.isatty(): |
| 25 | + return False |
| 26 | + except (AttributeError, ValueError): |
| 27 | + # Handle cases where .isatty() is not defined, or where e.g. |
| 28 | + # "ValueError: I/O operation on closed file" is raised |
| 29 | + return False |
| 30 | + |
| 31 | + if os.environ.get("TERM", "").lower() in {"dumb", "unknown"}: |
| 32 | + # Do not colour output if on a dumb terminal |
| 33 | + return False |
| 34 | + |
| 35 | + return True |
| 36 | + |
| 37 | + |
| 38 | +def disable_colour() -> None: |
| 39 | + global _COLOURING_DISABLED |
| 40 | + _COLOURING_DISABLED = True |
| 41 | + if sys.platform == 'win32': |
| 42 | + colorama.deinit() |
| 43 | + |
| 44 | + |
| 45 | +def enable_colour() -> None: |
| 46 | + global _COLOURING_DISABLED |
| 47 | + _COLOURING_DISABLED = False |
| 48 | + if sys.platform == 'win32': |
| 49 | + colorama.init() |
| 50 | + |
| 51 | + |
| 52 | +def colourise(colour_name: str, text: str, /) -> str: |
| 53 | + if _COLOURING_DISABLED: |
| 54 | + return text |
| 55 | + return globals()[colour_name](text) |
| 56 | + |
| 57 | + |
| 58 | +def _create_colour_func(escape_code: str, /) -> Callable[[str], str]: |
| 59 | + def inner(text: str) -> str: |
| 60 | + if _COLOURING_DISABLED: |
| 61 | + return text |
| 62 | + return f'\x1b[{escape_code}m{text}\x1b[39;49;00m' |
| 63 | + return inner |
| 64 | + |
| 65 | + |
| 66 | +# Wrap escape sequence with ``\1`` and ``\2`` to let readline know |
| 67 | +# that the colour escape codes are non-printable characters |
| 68 | +# [ https://tiswww.case.edu/php/chet/readline/readline.html ] |
| 69 | +# |
| 70 | +# Note: This does not work well in Windows |
| 71 | +# (see https://github.com/sphinx-doc/sphinx/pull/5059) |
| 72 | +if sys.platform == 'win32': |
| 73 | + _create_input_mode_colour_func = _create_colour_func |
| 74 | +else: |
| 75 | + def _create_input_mode_colour_func(escape_code: str, /) -> Callable[[str], str]: |
| 76 | + def inner(text: str) -> str: |
| 77 | + if _COLOURING_DISABLED: |
| 78 | + return text |
| 79 | + return f'\x01\x1b[{escape_code}m\x02{text}\x01\x1b[39;49;00m\x02' |
| 80 | + return inner |
| 81 | + |
| 82 | + |
| 83 | +reset = _create_colour_func('39;49;00') |
| 84 | +bold = _create_colour_func('01') |
| 85 | +# faint = _create_colour_func('02') |
| 86 | +# standout = _create_colour_func('03') |
| 87 | +underline = _create_colour_func('04') |
| 88 | +# blink = _create_colour_func('05') |
| 89 | + |
| 90 | +# black = _create_colour_func('30') |
| 91 | +darkgray = _create_colour_func('90') |
| 92 | +darkred = _create_colour_func('31') |
| 93 | +red = _create_colour_func('91') |
| 94 | +darkgreen = _create_colour_func('32') |
| 95 | +# green = _create_colour_func('92') |
| 96 | +brown = _create_colour_func('33') |
| 97 | +# yellow = _create_colour_func('93') |
| 98 | +# darkblue = _create_colour_func('34') |
| 99 | +blue = _create_colour_func('94') |
| 100 | +purple = _create_colour_func('35') |
| 101 | +# fuchsia = _create_colour_func('95') |
| 102 | +turquoise = _create_colour_func('36') |
| 103 | +# teal = _create_colour_func('96') |
| 104 | +# lightgray = _create_colour_func('37') |
| 105 | +# white = _create_colour_func('97') |
0 commit comments