Skip to content

Commit 407b00b

Browse files
committed
Refactor _cli.util.colour
1 parent af8d895 commit 407b00b

File tree

1 file changed

+72
-70
lines changed

1 file changed

+72
-70
lines changed

sphinx/_cli/util/colour.py

Lines changed: 72 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,99 @@
44

55
import os
66
import sys
7+
from typing import Callable
78

8-
try:
9-
# check if colorama is installed to support color on Windows
9+
if sys.platform == 'win32':
1010
import colorama
11-
except ImportError:
12-
colorama = None
1311

1412

15-
codes: dict[str, str] = {}
13+
_COLOURING_DISABLED = False
1614

1715

1816
def color_terminal() -> bool:
17+
"""Return True if coloured terminal output is supported."""
1918
if 'NO_COLOR' in os.environ:
2019
return False
21-
if sys.platform == 'win32' and colorama is not None:
22-
colorama.init()
23-
return True
2420
if 'FORCE_COLOR' in os.environ:
2521
return True
26-
if not hasattr(sys.stdout, 'isatty'):
22+
try:
23+
if not sys.stdout.isatty():
24+
return False
25+
except (AttributeError, ValueError):
26+
# Handle cases where .isatty() is not defined, or where e.g.
27+
# "ValueError: I/O operation on closed file" is raised
2728
return False
28-
if not sys.stdout.isatty():
29+
if os.environ.get("TERM", "").lower() in {"dumb", "unknown"}:
30+
# Do not colour output if on a dumb terminal
2931
return False
30-
if 'COLORTERM' in os.environ:
31-
return True
32-
term = os.environ.get('TERM', 'dumb').lower()
33-
if term in ('xterm', 'linux') or 'color' in term:
34-
return True
35-
return False
32+
if sys.platform == 'win32':
33+
colorama.init()
34+
return True
3635

3736

3837
def nocolor() -> None:
39-
if sys.platform == 'win32' and colorama is not None:
38+
global _COLOURING_DISABLED
39+
_COLOURING_DISABLED = True
40+
if sys.platform == 'win32':
4041
colorama.deinit()
41-
codes.clear()
4242

4343

4444
def coloron() -> None:
45-
codes.update(_orig_codes)
45+
global _COLOURING_DISABLED
46+
_COLOURING_DISABLED = False
47+
if sys.platform == 'win32':
48+
colorama.init()
4649

4750

4851
def colorize(name: str, text: str, input_mode: bool = False) -> str:
49-
def escseq(name: str) -> str:
50-
# Wrap escape sequence with ``\1`` and ``\2`` to let readline know
51-
# it is non-printable characters
52-
# ref: https://tiswww.case.edu/php/chet/readline/readline.html
53-
#
54-
# Note: This hack does not work well in Windows (see #5059)
55-
escape = codes.get(name, '')
56-
if input_mode and escape and sys.platform != 'win32':
57-
return '\1' + escape + '\2'
58-
else:
59-
return escape
60-
61-
return escseq(name) + text + escseq('reset')
62-
63-
64-
def create_color_func(name: str) -> None:
52+
if _COLOURING_DISABLED:
53+
return text
54+
55+
if sys.platform == 'win32' or not input_mode:
56+
return globals()[name](text)
57+
58+
# Wrap escape sequence with ``\1`` and ``\2`` to let readline know
59+
# it is non-printable characters
60+
# ref: https://tiswww.case.edu/php/chet/readline/readline.html
61+
#
62+
# Note: This does not work well in Windows (see
63+
# https://github.com/sphinx-doc/sphinx/pull/5059)
64+
escape_code = getattr(globals()[name], '__escape_code', '39;49;00')
65+
return f'\1\x1b[{escape_code}m\2{text}\1\x1b[39;49;00m\2'
66+
67+
68+
def _create_colour_func(
69+
__escape_code: str,
70+
) -> Callable[[str], str]:
6571
def inner(text: str) -> str:
66-
return colorize(name, text)
67-
globals()[name] = inner
68-
69-
70-
_attrs = {
71-
'reset': '39;49;00m',
72-
'bold': '01m',
73-
'faint': '02m',
74-
'standout': '03m',
75-
'underline': '04m',
76-
'blink': '05m',
77-
}
78-
79-
for _name, _value in _attrs.items():
80-
codes[_name] = '\x1b[' + _value
81-
82-
_colors = [
83-
('black', 'darkgray'),
84-
('darkred', 'red'),
85-
('darkgreen', 'green'),
86-
('brown', 'yellow'),
87-
('darkblue', 'blue'),
88-
('purple', 'fuchsia'),
89-
('turquoise', 'teal'),
90-
('lightgray', 'white'),
91-
]
92-
93-
for i, (dark, light) in enumerate(_colors, 30):
94-
codes[dark] = '\x1b[%im' % i
95-
codes[light] = '\x1b[%im' % (i + 60)
96-
97-
_orig_codes = codes.copy()
98-
99-
for _name in codes:
100-
create_color_func(_name)
72+
if _COLOURING_DISABLED:
73+
return text
74+
return f'\x1b[{__escape_code}m{text}\x1b[39;49;00m'
75+
# private attribute, only for ``colorize()``
76+
inner.__escape_code = __escape_code
77+
return inner
78+
79+
80+
reset = _create_colour_func('39;49;00')
81+
bold = _create_colour_func('01')
82+
# faint = _create_colour_func('02')
83+
# standout = _create_colour_func('03')
84+
# underline = _create_colour_func('04')
85+
# blink = _create_colour_func('05')
86+
87+
black = _create_colour_func('30')
88+
darkgray = _create_colour_func('90')
89+
darkred = _create_colour_func('31')
90+
red = _create_colour_func('91')
91+
darkgreen = _create_colour_func('32')
92+
green = _create_colour_func('92')
93+
brown = _create_colour_func('33')
94+
yellow = _create_colour_func('93')
95+
darkblue = _create_colour_func('34')
96+
blue = _create_colour_func('94')
97+
purple = _create_colour_func('35')
98+
fuchsia = _create_colour_func('95')
99+
turquoise = _create_colour_func('36')
100+
teal = _create_colour_func('96')
101+
lightgray = _create_colour_func('37')
102+
white = _create_colour_func('97')

0 commit comments

Comments
 (0)