Replace `colorize() with functions from _cli.util.colour` (#13259)

This commit is contained in:
Adam Turner 2025-01-21 20:38:10 +00:00 committed by GitHub
parent df962a4b03
commit 7154672c2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 49 deletions

View File

@ -2,27 +2,31 @@
from __future__ import annotations from __future__ import annotations
import os
import sys import sys
from collections.abc import Callable # NoQA: TC003 from os import environ as _environ
if False:
from collections.abc import Callable
if sys.platform == 'win32': if sys.platform == 'win32':
import colorama import colorama
colorama.just_fix_windows_console()
del colorama
_COLOURING_DISABLED = False _COLOURING_DISABLED = False
def terminal_supports_colour() -> bool: def terminal_supports_colour() -> bool:
"""Return True if coloured terminal output is supported.""" """Return True if coloured terminal output is supported."""
if 'NO_COLOUR' in os.environ or 'NO_COLOR' in os.environ: if 'NO_COLOUR' in _environ or 'NO_COLOR' in _environ:
return False return False
if sys.platform == 'win32': if sys.platform == 'win32':
colorama.just_fix_windows_console()
return True return True
if 'FORCE_COLOUR' in os.environ or 'FORCE_COLOR' in os.environ: if 'FORCE_COLOUR' in _environ or 'FORCE_COLOR' in _environ:
return True return True
if os.environ.get('CI', '') in {'true', '1'}: if _environ.get('CI', '').lower() in {'true', '1'}:
return True return True
try: try:
@ -34,7 +38,7 @@ def terminal_supports_colour() -> bool:
return False return False
# Do not colour output if on a dumb terminal # Do not colour output if on a dumb terminal
return os.environ.get('TERM', 'unknown').lower() not in {'dumb', 'unknown'} return _environ.get('TERM', 'unknown').lower() not in {'dumb', 'unknown'}
def disable_colour() -> None: def disable_colour() -> None:
@ -50,7 +54,21 @@ def enable_colour() -> None:
def colourise(colour_name: str, text: str, /) -> str: def colourise(colour_name: str, text: str, /) -> str:
if _COLOURING_DISABLED: if _COLOURING_DISABLED:
return text return text
return globals()[colour_name](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 _create_colour_func(escape_code: str, /) -> Callable[[str], str]:

View File

@ -10,35 +10,18 @@ import sys
import time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
# try to import readline, unix specific enhancement
try:
import readline
if TYPE_CHECKING and sys.platform == 'win32': # always false, for type checking
raise ImportError # NoQA: TRY301
READLINE_AVAILABLE = True
if readline.__doc__ and 'libedit' in readline.__doc__:
readline.parse_and_bind('bind ^I rl_complete')
USE_LIBEDIT = True
else:
readline.parse_and_bind('tab: complete')
USE_LIBEDIT = False
except ImportError:
READLINE_AVAILABLE = False
USE_LIBEDIT = False
from docutils.utils import column_width from docutils.utils import column_width
import sphinx.locale import sphinx.locale
from sphinx import __display_version__, package_dir from sphinx import __display_version__, package_dir
from sphinx._cli.util.colour import ( from sphinx._cli.util.colour import (
_create_input_mode_colour_func,
bold, bold,
disable_colour, disable_colour,
red, red,
terminal_supports_colour, terminal_supports_colour,
) )
from sphinx.locale import __ from sphinx.locale import __
from sphinx.util.console import colorize
from sphinx.util.osutil import ensuredir from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxRenderer from sphinx.util.template import SphinxRenderer
@ -46,6 +29,25 @@ if TYPE_CHECKING:
from collections.abc import Callable, Sequence from collections.abc import Callable, Sequence
from typing import Any from typing import Any
# try to import readline, unix specific enhancement
try:
import readline
if TYPE_CHECKING and sys.platform == 'win32':
# MyPy doesn't realise that this raises a ModuleNotFoundError
# on Windows, and complains that 'parse_and_bind' is not defined.
# This condition is always False at runtime, but tricks type checkers.
raise ImportError # NoQA: TRY301
except ImportError:
READLINE_AVAILABLE = USE_LIBEDIT = False
else:
READLINE_AVAILABLE = True
USE_LIBEDIT = 'libedit' in getattr(readline, '__doc__', '')
if USE_LIBEDIT:
readline.parse_and_bind('bind ^I rl_complete')
else:
readline.parse_and_bind('tab: complete')
EXTENSIONS = { EXTENSIONS = {
'autodoc': __('automatically insert docstrings from modules'), 'autodoc': __('automatically insert docstrings from modules'),
'doctest': __('automatically test code snippets in doctest blocks'), 'doctest': __('automatically test code snippets in doctest blocks'),
@ -73,10 +75,17 @@ DEFAULTS = {
PROMPT_PREFIX = '> ' PROMPT_PREFIX = '> '
if sys.platform == 'win32': if sys.platform == 'win32':
# On Windows, show questions as bold because of color scheme of PowerShell (refs: #5294). # On Windows, show questions as bold because of PowerShell's colour scheme
COLOR_QUESTION = 'bold' # (xref: https://github.com/sphinx-doc/sphinx/issues/5294).
from sphinx._cli.util.colour import bold as _question_colour
else: else:
COLOR_QUESTION = 'purple' from sphinx._cli.util.colour import purple as _question_colour
if READLINE_AVAILABLE:
# Use an input-mode colour function if readline is available
if escape_code := getattr(_question_colour, '__escape_code', ''):
_question_colour = _create_input_mode_colour_func(escape_code)
del escape_code
# function to get input from terminal -- overridden by the test suite # function to get input from terminal -- overridden by the test suite
@ -158,11 +167,8 @@ def do_prompt(
# sequence (see #5335). To avoid the problem, all prompts are not colored # sequence (see #5335). To avoid the problem, all prompts are not colored
# on libedit. # on libedit.
pass pass
elif READLINE_AVAILABLE:
# pass input_mode=True if readline available
prompt = colorize(COLOR_QUESTION, prompt, input_mode=True)
else: else:
prompt = colorize(COLOR_QUESTION, prompt, input_mode=False) prompt = _question_colour(prompt)
x = term_input(prompt).strip() x = term_input(prompt).strip()
if default and not x: if default and not x:
x = default x = default

View File

@ -12,8 +12,8 @@ from typing import TYPE_CHECKING
from docutils import nodes from docutils import nodes
from docutils.utils import get_source_line from docutils.utils import get_source_line
from sphinx._cli.util.colour import colourise
from sphinx.errors import SphinxWarning from sphinx.errors import SphinxWarning
from sphinx.util.console import colorize
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Iterator, Sequence, Set from collections.abc import Iterator, Sequence, Set
@ -49,14 +49,11 @@ VERBOSITY_MAP: defaultdict[int, int] = defaultdict(
}, },
) )
COLOR_MAP: defaultdict[int, str] = defaultdict( COLOR_MAP: dict[int, str] = {
lambda: 'blue', logging.ERROR: 'darkred',
{ logging.WARNING: 'red',
logging.ERROR: 'darkred', logging.DEBUG: 'darkgray',
logging.WARNING: 'red', }
logging.DEBUG: 'darkgray',
},
)
def getLogger(name: str) -> SphinxLoggerAdapter: def getLogger(name: str) -> SphinxLoggerAdapter:
@ -566,13 +563,14 @@ def get_node_location(node: Node) -> str | None:
class ColorizeFormatter(logging.Formatter): class ColorizeFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str: def format(self, record: logging.LogRecord) -> str:
message = super().format(record) message = super().format(record)
color = getattr(record, 'color', None) colour_name = getattr(record, 'color', '')
if color is None: if not colour_name:
color = COLOR_MAP.get(record.levelno) colour_name = COLOR_MAP.get(record.levelno, '')
if not colour_name:
if color: return message
return colorize(color, message) try:
else: return colourise(colour_name, message)
except ValueError:
return message return message