diff --git a/sphinx/_cli/util/colour.py b/sphinx/_cli/util/colour.py index 984748a361b..122b836129f 100644 --- a/sphinx/_cli/util/colour.py +++ b/sphinx/_cli/util/colour.py @@ -10,7 +10,7 @@ import colorama -_COLOURING_DISABLED = True +_COLOURING_DISABLED = False def terminal_supports_colour() -> bool: @@ -19,6 +19,7 @@ def terminal_supports_colour() -> bool: return False if sys.platform == 'win32': colorama.just_fix_windows_console() + return True if 'FORCE_COLOUR' in os.environ or 'FORCE_COLOR' in os.environ: return True if os.environ.get('CI', '') in {'true', '1'}: @@ -58,6 +59,7 @@ def inner(text: str) -> str: return text return f'\x1b[{escape_code}m{text}\x1b[39;49;00m' + inner.__escape_code = escape_code # type: ignore[attr-defined] return inner @@ -75,7 +77,7 @@ def _create_input_mode_colour_func(escape_code: str, /) -> Callable[[str], str]: def inner(text: str) -> str: if _COLOURING_DISABLED: return text - return f'\x01\x1b[{escape_code}m\x02{text}\x01\x1b[39;49;00m\x02' + return f'\1\x1b[{escape_code}m\2{text}\1\x1b[39;49;00m\2' return inner diff --git a/sphinx/application.py b/sphinx/application.py index 8cf47393c60..7067ff23973 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -16,6 +16,7 @@ import sphinx from sphinx import locale, package_dir +from sphinx._cli.util.colour import bold from sphinx.config import Config from sphinx.environment import BuildEnvironment from sphinx.errors import ApplicationError, ConfigError, VersionRequirementError @@ -27,7 +28,6 @@ from sphinx.util import docutils, logging from sphinx.util._pathlib import _StrPath, _StrPathProperty from sphinx.util.build_phase import BuildPhase -from sphinx.util.console import bold from sphinx.util.display import progress_message from sphinx.util.i18n import CatalogRepository from sphinx.util.logging import prefixed_warnings diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 38fb9b3257a..89d518f8539 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -13,6 +13,7 @@ from docutils import nodes from docutils.utils import DependencyList +from sphinx._cli.util.colour import bold from sphinx.environment import ( CONFIG_CHANGED_REASON, CONFIG_OK, @@ -29,7 +30,6 @@ from sphinx.util._importer import import_object from sphinx.util._pathlib import _StrPathProperty from sphinx.util.build_phase import BuildPhase -from sphinx.util.console import bold from sphinx.util.display import progress_message, status_iterator from sphinx.util.docutils import sphinx_domains from sphinx.util.i18n import CatalogRepository, docname_to_domain diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index b9341df90a6..585bdf3f748 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -7,11 +7,11 @@ from typing import TYPE_CHECKING from sphinx import package_dir +from sphinx._cli.util.colour import bold from sphinx.builders import Builder from sphinx.locale import _, __ from sphinx.theming import HTMLThemeFactory from sphinx.util import logging -from sphinx.util.console import bold from sphinx.util.fileutil import copy_asset_file from sphinx.util.osutil import ensuredir, os_path diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 9b6145fbe8a..dbb593f8f03 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -14,11 +14,11 @@ from docutils import nodes from sphinx import addnodes, package_dir +from sphinx._cli.util.colour import bold from sphinx.builders import Builder from sphinx.errors import ThemeError from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import bold from sphinx.util.display import status_iterator from sphinx.util.i18n import docname_to_domain from sphinx.util.index_entries import split_index_msg diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 886840c37bf..e2480c6909d 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -23,6 +23,7 @@ from sphinx import __display_version__, package_dir from sphinx import version_info as sphinx_version +from sphinx._cli.util.colour import bold from sphinx.builders import Builder from sphinx.builders.html._assets import ( _CascadingStyleSheet, @@ -45,7 +46,6 @@ from sphinx.util._pathlib import _StrPath from sphinx.util._timestamps import _format_rfc3339_microseconds from sphinx.util._uri import is_url -from sphinx.util.console import bold from sphinx.util.display import progress_message, status_iterator from sphinx.util.docutils import new_document from sphinx.util.fileutil import copy_asset diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 268add92fb9..2bec3c010dc 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -12,6 +12,7 @@ import sphinx.builders.latex.nodes # NoQA: F401 # Workaround: import this before writer to avoid ImportError from sphinx import addnodes, highlighting, package_dir +from sphinx._cli.util.colour import darkgreen from sphinx.builders import Builder from sphinx.builders.latex.constants import ( ADDITIONAL_SETTINGS, @@ -25,7 +26,6 @@ from sphinx.errors import NoUri, SphinxError from sphinx.locale import _, __ from sphinx.util import logging, texescape -from sphinx.util.console import darkgreen from sphinx.util.display import progress_message, status_iterator from sphinx.util.docutils import SphinxFileOutput, new_document from sphinx.util.fileutil import copy_asset_file diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 4b8dd2b0e93..4fa8e0933b2 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -24,12 +24,12 @@ ) from requests.exceptions import Timeout as RequestTimeout +from sphinx._cli.util.colour import darkgray, darkgreen, purple, red, turquoise from sphinx.builders.dummy import DummyBuilder from sphinx.locale import __ from sphinx.transforms.post_transforms import SphinxPostTransform from sphinx.util import logging, requests from sphinx.util._uri import encode_uri -from sphinx.util.console import darkgray, darkgreen, purple, red, turquoise from sphinx.util.http_date import rfc1123_to_epoch from sphinx.util.nodes import get_node_line diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index d0c14418775..a60a8bb9c28 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -10,10 +10,10 @@ from docutils.io import FileOutput from sphinx import addnodes +from sphinx._cli.util.colour import darkgreen from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import darkgreen from sphinx.util.display import progress_message from sphinx.util.nodes import inline_all_toctrees from sphinx.util.osutil import ensuredir, make_filename_from_project diff --git a/sphinx/builders/singlehtml.py b/sphinx/builders/singlehtml.py index d3b8ed77bd9..1caeca42cca 100644 --- a/sphinx/builders/singlehtml.py +++ b/sphinx/builders/singlehtml.py @@ -7,12 +7,12 @@ from docutils import nodes +from sphinx._cli.util.colour import darkgreen from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.deprecation import RemovedInSphinx10Warning from sphinx.environment.adapters.toctree import global_toctree_for_doc from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import darkgreen from sphinx.util.display import progress_message from sphinx.util.nodes import inline_all_toctrees diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 5465e258857..761831e0d1d 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -12,12 +12,12 @@ from docutils.io import FileOutput from sphinx import addnodes, package_dir +from sphinx._cli.util.colour import darkgreen from sphinx.builders import Builder from sphinx.environment.adapters.asset import ImageAdapter from sphinx.errors import NoUri from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.console import darkgreen from sphinx.util.display import progress_message, status_iterator from sphinx.util.docutils import new_document from sphinx.util.nodes import inline_all_toctrees diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index dfd54ee029c..11a70df0c6c 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -13,12 +13,11 @@ import sphinx._cli.util.errors import sphinx.locale from sphinx import __display_version__ -from sphinx._cli.util.colour import terminal_supports_colour +from sphinx._cli.util.colour import disable_colour, terminal_supports_colour from sphinx.application import Sphinx from sphinx.locale import __ from sphinx.util._io import TeeStripANSI from sphinx.util._pathlib import _StrPath -from sphinx.util.console import nocolor from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.util.osutil import ensuredir @@ -328,7 +327,7 @@ def _validate_filenames( def _validate_colour_support(colour: str) -> None: if colour == 'no' or (colour == 'auto' and not terminal_supports_colour()): - nocolor() + disable_colour() def _parse_logging( diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py index b65117e8215..97eceda1931 100644 --- a/sphinx/cmd/make_mode.py +++ b/sphinx/cmd/make_mode.py @@ -16,10 +16,9 @@ from typing import TYPE_CHECKING import sphinx -from sphinx._cli.util.colour import terminal_supports_colour +from sphinx._cli.util.colour import blue, bold, disable_colour, terminal_supports_colour from sphinx.cmd.build import build_main from sphinx.util._pathlib import _StrPath -from sphinx.util.console import blue, bold, nocolor from sphinx.util.osutil import rmtree if TYPE_CHECKING: @@ -93,7 +92,7 @@ def build_clean(self) -> int: def build_help(self) -> None: if not terminal_supports_colour(): - nocolor() + disable_colour() print(bold('Sphinx v%s' % sphinx.__display_version__)) print("Please use `make %s' where %s is one of" % ((blue('target'),) * 2)) diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 51e642d750c..06a89b995e4 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -31,9 +31,14 @@ import sphinx.locale from sphinx import __display_version__, package_dir -from sphinx._cli.util.colour import terminal_supports_colour +from sphinx._cli.util.colour import ( + bold, + disable_colour, + red, + terminal_supports_colour, +) from sphinx.locale import __ -from sphinx.util.console import bold, colorize, nocolor, red +from sphinx.util.console import colorize from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxRenderer @@ -726,7 +731,7 @@ def main(argv: Sequence[str] = (), /) -> int: sphinx.locale.init_console() if not terminal_supports_colour(): - nocolor() + disable_colour() # parse options parser = get_parser() diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index a7c6c65383f..4911458e319 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -17,10 +17,10 @@ from typing import TYPE_CHECKING import sphinx +from sphinx._cli.util.colour import red from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import red from sphinx.util.inspect import safe_getattr if TYPE_CHECKING: diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 5e4b3831063..7079834edb1 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -19,10 +19,10 @@ from packaging.version import Version import sphinx +from sphinx._cli.util.colour import bold from sphinx.builders import Builder from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import bold from sphinx.util.docutils import SphinxDirective from sphinx.util.osutil import relpath diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 4b0fe5bc736..66a2a22fa03 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -15,7 +15,7 @@ import sphinx.application import sphinx.locale import sphinx.pycode -from sphinx.util.console import strip_colors +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.util.docutils import additional_nodes if TYPE_CHECKING: @@ -271,7 +271,11 @@ def _clean_up_global_state() -> None: # deprecated name -> (object to return, canonical path or '', removal version) _DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = { - 'strip_escseq': (strip_colors, 'sphinx.util.console.strip_colors', (9, 0)), + 'strip_escseq': ( + strip_escape_sequences, + 'sphinx.util.console.strip_escape_sequences', + (9, 0), + ), } diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index f804fb0ee65..65f47d31f9e 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -127,10 +127,10 @@ def __getattr__(name: str) -> Any: return patfilter - if name == 'strip_colors': - from sphinx.util.console import strip_colors + if name == 'strip_escape_sequences': + from sphinx._cli.util.errors import strip_escape_sequences - return strip_colors + return strip_escape_sequences if name in { 'caption_ref_re', diff --git a/sphinx/util/_io.py b/sphinx/util/_io.py index b85f49ded26..94ad9f8f0d0 100644 --- a/sphinx/util/_io.py +++ b/sphinx/util/_io.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from sphinx.util.console import strip_escape_sequences +from sphinx._cli.util.errors import strip_escape_sequences if TYPE_CHECKING: from typing import Protocol diff --git a/sphinx/util/console.py b/sphinx/util/console.py index bf3b2dbbe1a..fadf425fa26 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -2,74 +2,39 @@ from __future__ import annotations -import re import shutil -import sys -from typing import TYPE_CHECKING -from sphinx._cli.util.colour import ( - terminal_supports_colour as color_terminal, # NoQA: F401 +import sphinx._cli.util.colour +from sphinx._cli.util.colour import ( # NoQA: F401 + _create_input_mode_colour_func, + colourise, + darkblue, + darkgray, + darkgreen, + darkred, + disable_colour, + enable_colour, + faint, + fuchsia, + green, + lightgray, + purple, + red, + reset, + standout, + teal, + terminal_supports_colour, + turquoise, + underline, + white, + yellow, ) +from sphinx._cli.util.errors import strip_escape_sequences -if TYPE_CHECKING: - from typing import Final - - # fmt: off - def reset(text: str) -> str: ... - def bold(text: str) -> str: ... - def faint(text: str) -> str: ... - def standout(text: str) -> str: ... - def underline(text: str) -> str: ... - def blink(text: str) -> str: ... - - def black(text: str) -> str: ... - def white(text: str) -> str: ... - def red(text: str) -> str: ... - def green(text: str) -> str: ... - def yellow(text: str) -> str: ... - def blue(text: str) -> str: ... - def fuchsia(text: str) -> str: ... - def teal(text: str) -> str: ... - - def darkgray(text: str) -> str: ... - def lightgray(text: str) -> str: ... - def darkred(text: str) -> str: ... - def darkgreen(text: str) -> str: ... - def brown(text: str) -> str: ... - def darkblue(text: str) -> str: ... - def purple(text: str) -> str: ... - def turquoise(text: str) -> str: ... - # fmt: on - -try: - # check if colorama is installed to support color on Windows - import colorama - - COLORAMA_AVAILABLE = True -except ImportError: - COLORAMA_AVAILABLE = False - -_CSI: Final[str] = re.escape('\x1b[') # 'ESC [': Control Sequence Introducer - -# Pattern matching ANSI control sequences containing colors. -_ansi_color_re: Final[re.Pattern[str]] = re.compile(r'\x1b\[(?:\d+;){0,2}\d*m') - -_ansi_re: Final[re.Pattern[str]] = re.compile( - _CSI - + r""" - (?: - (?:\d+;){0,2}\d*m # ANSI color code ('m' is equivalent to '0m') - | - [012]?K # ANSI Erase in Line ('K' is equivalent to '0K') - )""", - re.VERBOSE | re.ASCII, -) -"""Pattern matching ANSI CSI colors (SGR) and erase line (EL) sequences. - -See :func:`strip_escape_sequences` for details. -""" - -codes: dict[str, str] = {} +color_terminal = terminal_supports_colour +nocolor = disable_colour +coloron = enable_colour +strip_colors = strip_escape_sequences def terminal_safe(s: str) -> str: @@ -86,7 +51,7 @@ def get_terminal_width() -> int: def term_width_line(text: str) -> str: - if not codes: + if sphinx._cli.util.colour._COLOURING_DISABLED: # if no coloring, don't output fancy backspaces return text + '\n' else: @@ -94,103 +59,13 @@ def term_width_line(text: str) -> str: return text.ljust(_tw + len(text) - len(strip_escape_sequences(text))) + '\r' -def nocolor() -> None: - if sys.platform == 'win32' and COLORAMA_AVAILABLE: - colorama.deinit() - codes.clear() - - -def coloron() -> None: - codes.update(_orig_codes) - - def colorize(name: str, text: str, input_mode: bool = False) -> str: - def escseq(name: str) -> str: - # Wrap escape sequence with ``\1`` and ``\2`` to let readline know - # it is non-printable characters - # ref: https://tiswww.case.edu/php/chet/readline/readline.html - # - # Note: This hack does not work well in Windows (see #5059) - escape = codes.get(name, '') - if input_mode and escape and sys.platform != 'win32': - return '\1' + escape + '\2' - else: - return escape - - return escseq(name) + text + escseq('reset') - - -def strip_colors(s: str) -> str: - """Remove the ANSI color codes in a string *s*. - - .. caution:: - - This function is not meant to be used in production and should only - be used for testing Sphinx's output messages. - - .. seealso:: :func:`strip_escape_sequences` - """ - return _ansi_color_re.sub('', s) - - -def strip_escape_sequences(text: str, /) -> str: - r"""Remove the ANSI CSI colors and "erase in line" sequences. - - Other `escape sequences `__ (e.g., VT100-specific functions) are not - supported and only control sequences *natively* known to Sphinx (i.e., - colors declared in this module and "erase entire line" (``'\x1b[2K'``)) - are eliminated by this function. - - .. caution:: - - This function is not meant to be used in production and should only - be used for testing Sphinx's output messages that were not tempered - with by third-party extensions. - - .. versionadded:: 7.3 - - This function is added as an *experimental* feature. - - __ https://en.wikipedia.org/wiki/ANSI_escape_code - """ - return _ansi_re.sub('', text) - - -def create_color_func(name: str) -> None: - def inner(text: str) -> str: - return colorize(name, text) - - globals()[name] = inner - - -_attrs = { - 'reset': '39;49;00m', - 'bold': '01m', - 'faint': '02m', - 'standout': '03m', - 'underline': '04m', - 'blink': '05m', -} - -for __name, __value in _attrs.items(): - codes[__name] = '\x1b[' + __value - -_colors = [ - ('black', 'darkgray'), - ('darkred', 'red'), - ('darkgreen', 'green'), - ('brown', 'yellow'), - ('darkblue', 'blue'), - ('purple', 'fuchsia'), - ('turquoise', 'teal'), - ('lightgray', 'white'), -] - -for __i, (__dark, __light) in enumerate(_colors, 30): - codes[__dark] = '\x1b[%im' % __i - codes[__light] = '\x1b[%im' % (__i + 60) - -_orig_codes = codes.copy() - -for _name in codes: - create_color_func(_name) + if input_mode: + colour_func = globals()[name] + escape_code = getattr(colour_func, '__escape_code', '') + if not escape_code: + return colour_func(text) + inner = _create_input_mode_colour_func(escape_code) + return inner(text) + + return colourise(name, text) diff --git a/sphinx/util/display.py b/sphinx/util/display.py index c1c618950f1..196b0f128d1 100644 --- a/sphinx/util/display.py +++ b/sphinx/util/display.py @@ -2,10 +2,9 @@ import functools -from sphinx._cli.util.colour import terminal_supports_colour +from sphinx._cli.util.colour import bold, terminal_supports_colour from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.console import bold if False: from collections.abc import Callable, Iterable, Iterator diff --git a/tests/test__cli/__init__.py b/tests/test__cli/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_util/test_util_console.py b/tests/test__cli/test__cli_util_errors.py similarity index 60% rename from tests/test_util/test_util_console.py rename to tests/test__cli/test__cli_util_errors.py index d9140a6b9c2..cfe4ad8afa6 100644 --- a/tests/test_util/test_util_console.py +++ b/tests/test__cli/test__cli_util_errors.py @@ -4,45 +4,38 @@ import operator from typing import TYPE_CHECKING -import pytest - -from sphinx.util.console import blue, reset, strip_colors, strip_escape_sequences +from sphinx._cli.util.colour import blue, reset +from sphinx._cli.util.errors import strip_escape_sequences if TYPE_CHECKING: - from collections.abc import Callable, Sequence + from collections.abc import Sequence from typing import Final CURSOR_UP: Final[str] = '\x1b[2A' # ignored ANSI code ERASE_LINE: Final[str] = '\x1b[2K' # supported ANSI code -TEXT: Final[str] = '\x07 Hello world!' - - -@pytest.mark.parametrize( - ('strip_function', 'ansi_base_blocks', 'text_base_blocks'), - [ - ( - strip_colors, - # double ERASE_LINE so that the tested strings may have 2 of them - [TEXT, blue(TEXT), reset(TEXT), ERASE_LINE, ERASE_LINE, CURSOR_UP], - # :func:`strip_colors` removes color codes but keeps ERASE_LINE and CURSOR_UP - [TEXT, TEXT, TEXT, ERASE_LINE, ERASE_LINE, CURSOR_UP], - ), - ( - strip_escape_sequences, - # double ERASE_LINE so that the tested strings may have 2 of them - [TEXT, blue(TEXT), reset(TEXT), ERASE_LINE, ERASE_LINE, CURSOR_UP], - # :func:`strip_escape_sequences` strips ANSI codes known by Sphinx - [TEXT, TEXT, TEXT, '', '', CURSOR_UP], - ), - ], - ids=[strip_colors.__name__, strip_escape_sequences.__name__], -) -def test_strip_ansi( - strip_function: Callable[[str], str], - ansi_base_blocks: Sequence[str], - text_base_blocks: Sequence[str], -) -> None: - assert callable(strip_function) +TEXT: Final[str] = '\x07 ß Hello world!' + + +def test_strip_escape_sequences() -> None: + # double ERASE_LINE so that the tested strings may have 2 of them + ansi_base_blocks = [ + TEXT, + blue(TEXT), + reset(TEXT), + ERASE_LINE, + ERASE_LINE, + CURSOR_UP, + ] + # :func:`strip_escape_sequences` strips ANSI codes known by Sphinx + text_base_blocks = [ + TEXT, + TEXT, + TEXT, + '', + '', + CURSOR_UP, + ] + assert len(text_base_blocks) == len(ansi_base_blocks) N = len(ansi_base_blocks) @@ -67,7 +60,7 @@ def next_ansi_blocks(choices: Sequence[str], n: int) -> Sequence[str]: ansi_string = glue.join(ansi_strings) text_string = glue.join(text_strings) - assert strip_function(ansi_string) == text_string + assert strip_escape_sequences(ansi_string) == text_string def test_strip_ansi_short_forms(): @@ -76,7 +69,7 @@ def test_strip_ansi_short_forms(): # some messages use '\x1b[0m' instead of ``reset(s)``, so we # test whether this alternative form is supported or not. - for strip_function in strip_colors, strip_escape_sequences: + for strip_function in strip_escape_sequences, strip_escape_sequences: # \x1b[m and \x1b[0m are equivalent to \x1b[00m assert strip_function('\x1b[m') == '' assert strip_function('\x1b[0m') == '' diff --git a/tests/test_application.py b/tests/test_application.py index c4b11b64127..8cfde31ff39 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -13,10 +13,10 @@ from docutils import nodes import sphinx.application +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.errors import ExtensionError from sphinx.testing.util import SphinxTestApp from sphinx.util import logging -from sphinx.util.console import strip_colors if TYPE_CHECKING: import os @@ -84,14 +84,14 @@ def test_emit_with_nonascii_name_node(app): @pytest.mark.sphinx('html', testroot='root') def test_extensions(app): app.setup_extension('shutil') - warning = strip_colors(app.warning.getvalue()) + warning = strip_escape_sequences(app.warning.getvalue()) assert "extension 'shutil' has no setup() function" in warning @pytest.mark.sphinx('html', testroot='root') def test_extension_in_blacklist(app): app.setup_extension('sphinxjp.themecore') - msg = strip_colors(app.warning.getvalue()) + msg = strip_escape_sequences(app.warning.getvalue()) assert msg.startswith("WARNING: the extension 'sphinxjp.themecore' was") diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py index 097f895d7c8..b82f561655a 100644 --- a/tests/test_builders/test_build_html.py +++ b/tests/test_builders/test_build_html.py @@ -9,9 +9,9 @@ import pytest +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.errors import ConfigError -from sphinx.util.console import strip_colors from sphinx.util.inventory import InventoryFile from tests.test_builders.xpath_data import FIGURE_CAPTION @@ -512,7 +512,7 @@ def test_validate_html_extra_path(app): validate_html_extra_path(app, app.config) assert app.config.html_extra_path == ['_static'] - warnings = strip_colors(app.warning.getvalue()).splitlines() + warnings = strip_escape_sequences(app.warning.getvalue()).splitlines() assert "html_extra_path entry '/path/to/not_found' does not exist" in warnings[0] assert warnings[1].endswith(' is placed inside outdir') assert warnings[2].endswith(' does not exist') @@ -536,7 +536,7 @@ def test_validate_html_static_path(app): validate_html_static_path(app, app.config) assert app.config.html_static_path == ['_static'] - warnings = strip_colors(app.warning.getvalue()).splitlines() + warnings = strip_escape_sequences(app.warning.getvalue()).splitlines() assert "html_static_path entry '/path/to/not_found' does not exist" in warnings[0] assert warnings[1].endswith(' is placed inside outdir') assert warnings[2].endswith(' does not exist') @@ -598,7 +598,7 @@ def handler(app): app.build() assert not target.exists() - ws = strip_colors(app.warning.getvalue()).splitlines() + ws = strip_escape_sequences(app.warning.getvalue()).splitlines() assert len(ws) >= 1 file = os.fsdecode(target) diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 120575b57ed..56d42afea45 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -19,6 +19,7 @@ from urllib3.poolmanager import PoolManager import sphinx.util.http_date +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.builders.linkcheck import ( CheckRequest, Hyperlink, @@ -28,7 +29,6 @@ ) from sphinx.util import requests from sphinx.util._pathlib import _StrPath -from sphinx.util.console import strip_colors from tests.utils import CERT_FILE, serve_application @@ -775,7 +775,7 @@ def test_linkcheck_allowed_redirects(app: SphinxTestApp) -> None: assert ( f'index.rst:3: WARNING: redirect http://{address}/path2 - with Found to ' f'http://{address}/?redirected=1\n' - ) in strip_colors(app.warning.getvalue()) + ) in strip_escape_sequences(app.warning.getvalue()) assert len(app.warning.getvalue().splitlines()) == 1 @@ -1061,7 +1061,7 @@ def test_too_many_requests_retry_after_int_delay(app, capsys): 'info': '', } rate_limit_log = f'-rate limited- http://{address}/ | sleeping...\n' - assert rate_limit_log in strip_colors(app.status.getvalue()) + assert rate_limit_log in strip_escape_sequences(app.status.getvalue()) _stdout, stderr = capsys.readouterr() assert stderr == textwrap.dedent( """\ diff --git a/tests/test_builders/test_build_warnings.py b/tests/test_builders/test_build_warnings.py index cf307cbbf0c..7178abb646b 100644 --- a/tests/test_builders/test_build_warnings.py +++ b/tests/test_builders/test_build_warnings.py @@ -7,8 +7,8 @@ import pytest +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.errors import SphinxError -from sphinx.util.console import strip_colors ENV_WARNINGS = """\ {root}/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ @@ -55,7 +55,7 @@ def _check_warnings(expected_warnings: str, warning: str) -> None: - warnings = strip_colors(re.sub(re.escape(os.sep) + '{1,2}', '/', warning)) + warnings = strip_escape_sequences(re.sub(re.escape(os.sep) + '{1,2}', '/', warning)) assert re.match(f'{expected_warnings}$', warnings), ( "Warnings don't match:\n" f'--- Expected (regex):\n{expected_warnings}\n' @@ -127,7 +127,7 @@ def setup(app): tmp_path.joinpath('index.rst').write_text('Test\n====\n', encoding='utf-8') app = make_app(srcdir=tmp_path) app.build() - assert strip_colors(app.warning.getvalue()).strip() == ( + assert strip_escape_sequences(app.warning.getvalue()).strip() == ( "WARNING: cannot cache unpickable configuration value: 'my_config' " '(because it contains a function, class, or module object) [config.cache]' ) diff --git a/tests/test_environment/test_environment.py b/tests/test_environment/test_environment.py index a32e6999146..a5059e3e930 100644 --- a/tests/test_environment/test_environment.py +++ b/tests/test_environment/test_environment.py @@ -7,6 +7,7 @@ import pytest +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder from sphinx.config import Config @@ -17,7 +18,6 @@ CONFIG_OK, _differing_config_keys, ) -from sphinx.util.console import strip_colors @pytest.mark.sphinx('dummy', testroot='basic') @@ -28,7 +28,7 @@ def test_config_status(make_app, app_params): app1 = make_app(*args, freshenv=True, **kwargs) assert app1.env.config_status == CONFIG_NEW app1.build() - output = strip_colors(app1.status.getvalue()) + output = strip_escape_sequences(app1.status.getvalue()) # assert 'The configuration has changed' not in output assert '[new config] 1 added' in output @@ -36,7 +36,7 @@ def test_config_status(make_app, app_params): app2 = make_app(*args, **kwargs) assert app2.env.config_status == CONFIG_OK app2.build() - output = strip_colors(app2.status.getvalue()) + output = strip_escape_sequences(app2.status.getvalue()) assert 'The configuration has changed' not in output assert '0 added, 0 changed, 0 removed' in output @@ -49,7 +49,7 @@ def test_config_status(make_app, app_params): assert app3.env.config_status == CONFIG_CHANGED app3.build() shutil.move(other_fname, fname) - output = strip_colors(app3.status.getvalue()) + output = strip_escape_sequences(app3.status.getvalue()) assert 'The configuration has changed' in output assert "[config changed ('master_doc')] 1 added," in output @@ -60,7 +60,7 @@ def test_config_status(make_app, app_params): assert app4.env.config_status == CONFIG_EXTENSIONS_CHANGED app4.build() want_str = "[extensions changed ('sphinx.ext.autodoc')] 1 added" - output = strip_colors(app4.status.getvalue()) + output = strip_escape_sequences(app4.status.getvalue()) assert 'The configuration has changed' not in output assert want_str in output diff --git a/tests/test_extensions/test_ext_intersphinx.py b/tests/test_extensions/test_ext_intersphinx.py index d93ffadfb2a..f6a876402ac 100644 --- a/tests/test_extensions/test_ext_intersphinx.py +++ b/tests/test_extensions/test_ext_intersphinx.py @@ -11,6 +11,7 @@ from docutils import nodes from sphinx import addnodes +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.builders.html import INVENTORY_FILENAME from sphinx.config import Config from sphinx.errors import ConfigError @@ -29,7 +30,6 @@ _strip_basic_auth, ) from sphinx.ext.intersphinx._shared import _IntersphinxProject -from sphinx.util.console import strip_colors from tests.test_util.intersphinx_data import ( INVENTORY_V2, @@ -532,7 +532,7 @@ def test_validate_intersphinx_mapping_warnings(app): match=r'^Invalid `intersphinx_mapping` configuration \(16 errors\).$', ): validate_intersphinx_mapping(app, app.config) - warnings = strip_colors(app.warning.getvalue()).splitlines() + warnings = strip_escape_sequences(app.warning.getvalue()).splitlines() assert len(warnings) == len(bad_intersphinx_mapping) - 3 assert warnings == [ "ERROR: Invalid intersphinx project identifier `''` in intersphinx_mapping. Project identifiers must be non-empty strings.", @@ -707,7 +707,7 @@ def test_intersphinx_role(app): app.build() content = (app.outdir / 'index.html').read_text(encoding='utf8') - warnings = strip_colors(app.warning.getvalue()).splitlines() + warnings = strip_escape_sequences(app.warning.getvalue()).splitlines() index_path = app.srcdir / 'index.rst' assert warnings == [ f"{index_path}:21: WARNING: role for external cross-reference not found in domain 'py': 'nope' [intersphinx.external]", diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py index b22738e022b..3f25773ec91 100644 --- a/tests/test_intl/test_intl.py +++ b/tests/test_intl/test_intl.py @@ -18,8 +18,8 @@ from docutils import nodes from sphinx import locale +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.testing.util import assert_node, etree_parse -from sphinx.util.console import strip_colors from sphinx.util.nodes import NodeMatcher if TYPE_CHECKING: @@ -1893,7 +1893,7 @@ def test_image_glob_intl_using_figure_language_filename(app): def getwarning(warnings: StringIO) -> str: - return strip_colors(warnings.getvalue().replace(os.sep, '/')) + return strip_escape_sequences(warnings.getvalue().replace(os.sep, '/')) @pytest.mark.sphinx( diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 659fbe58641..c9c205db8c0 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -8,9 +8,9 @@ import pytest +from sphinx._cli.util.colour import disable_colour, enable_colour from sphinx.cmd import quickstart as qs from sphinx.testing.util import SphinxTestApp -from sphinx.util.console import coloron, nocolor if TYPE_CHECKING: from collections.abc import Callable @@ -21,7 +21,7 @@ def setup_module(): - nocolor() + disable_colour() def mock_input( @@ -50,7 +50,7 @@ def input_(prompt: str) -> str: def teardown_module(): qs.term_input = real_input - coloron() + enable_colour() def test_do_prompt(): diff --git a/tests/test_transforms/test_unreferenced_footnotes.py b/tests/test_transforms/test_unreferenced_footnotes.py index ac1b8b15dbd..c9810ad607b 100644 --- a/tests/test_transforms/test_unreferenced_footnotes.py +++ b/tests/test_transforms/test_unreferenced_footnotes.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from sphinx.util.console import strip_colors +from sphinx._cli.util.errors import strip_escape_sequences if TYPE_CHECKING: from pathlib import Path @@ -35,7 +35,7 @@ def test_warnings(make_app: type[SphinxTestApp], tmp_path: Path) -> None: ) app = make_app(srcdir=tmp_path) app.build() - warnings = strip_colors(app.warning.getvalue()).lstrip() + warnings = strip_escape_sequences(app.warning.getvalue()).lstrip() warnings = warnings.replace(str(tmp_path / 'index.rst'), 'source/index.rst') assert ( warnings diff --git a/tests/test_util/test_util.py b/tests/test_util/test_util.py index 745b75c617c..a78e4151fda 100644 --- a/tests/test_util/test_util.py +++ b/tests/test_util/test_util.py @@ -5,13 +5,13 @@ import pytest import sphinx.util +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.deprecation import RemovedInSphinx10Warning, RemovedInSphinx90Warning from sphinx.errors import ExtensionError from sphinx.util._files import DownloadFiles, FilenameUniqDict from sphinx.util._importer import import_object from sphinx.util._lines import parse_line_num_spec from sphinx.util._uri import encode_uri, is_url -from sphinx.util.console import strip_colors from sphinx.util.index_entries import _split_into, split_index_msg from sphinx.util.matching import patfilter from sphinx.util.nodes import ( @@ -78,7 +78,7 @@ def test_exported_attributes(): assert sphinx.util.isurl is is_url assert sphinx.util.parselinenos is parse_line_num_spec assert sphinx.util.patfilter is patfilter - assert sphinx.util.strip_colors is strip_colors + assert sphinx.util.strip_escape_sequences is strip_escape_sequences assert sphinx.util.caption_ref_re is caption_ref_re assert sphinx.util.explicit_title_re is explicit_title_re assert sphinx.util.nested_parse_with_titles is nested_parse_with_titles diff --git a/tests/test_util/test_util_display.py b/tests/test_util/test_util_display.py index e0cb7a47564..0428a6c7c99 100644 --- a/tests/test_util/test_util_display.py +++ b/tests/test_util/test_util_display.py @@ -4,8 +4,8 @@ import pytest +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.util import logging -from sphinx.util.console import strip_colors from sphinx.util.display import ( SkipProgressMessage, display_chunk, @@ -30,7 +30,7 @@ def test_status_iterator_length_0(app): app.status.seek(0) app.status.truncate(0) yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... ')) - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing ... hello sphinx world \n' in output assert yields == ['hello', 'sphinx', 'world'] @@ -47,7 +47,7 @@ def test_status_iterator_verbosity_0(app, monkeypatch): ['hello', 'sphinx', 'world'], 'testing ... ', length=3, verbosity=0 ) assert list(yields) == ['hello', 'sphinx', 'world'] - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing ... [ 33%] hello\r' in output assert 'testing ... [ 67%] sphinx\r' in output assert 'testing ... [100%] world\r\n' in output @@ -65,7 +65,7 @@ def test_status_iterator_verbosity_1(app, monkeypatch): ['hello', 'sphinx', 'world'], 'testing ... ', length=3, verbosity=1 ) assert list(yields) == ['hello', 'sphinx', 'world'] - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing ... [ 33%] hello\n' in output assert 'testing ... [ 67%] sphinx\n' in output assert 'testing ... [100%] world\n\n' in output @@ -80,14 +80,14 @@ def test_progress_message(app): with progress_message('testing'): logger.info('blah ', nonl=True) - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing... blah done\n' in output # skipping case with progress_message('testing'): raise SkipProgressMessage('Reason: %s', 'error') # NoQA: EM101,TRY003 - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing... skipped\nReason: error\n' in output # error case @@ -97,7 +97,7 @@ def test_progress_message(app): except Exception: pass - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing... failed\n' in output # decorator @@ -106,5 +106,5 @@ def func(): logger.info('in func ', nonl=True) func() - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert 'testing... in func done\n' in output diff --git a/tests/test_util/test_util_fileutil.py b/tests/test_util/test_util_fileutil.py index ebe252f6264..32a20de1bbe 100644 --- a/tests/test_util/test_util_fileutil.py +++ b/tests/test_util/test_util_fileutil.py @@ -7,8 +7,8 @@ import pytest +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.jinja2glue import BuiltinTemplateLoader -from sphinx.util.console import strip_colors from sphinx.util.fileutil import _template_basename, copy_asset, copy_asset_file @@ -127,7 +127,7 @@ def test_copy_asset_template(app): app.build(force_all=True) expected_msg = r'^Writing evaluated template result to [^\n]*\bAPI.html$' - output = strip_colors(app.status.getvalue()) + output = strip_escape_sequences(app.status.getvalue()) assert re.findall(expected_msg, output, flags=re.MULTILINE) @@ -136,7 +136,7 @@ def test_copy_asset_overwrite(app): app.build() src = app.srcdir / 'myext_static' / 'custom-styles.css' dst = app.outdir / '_static' / 'custom-styles.css' - assert strip_colors(app.warning.getvalue()) == ( + assert strip_escape_sequences(app.warning.getvalue()) == ( f'WARNING: Aborted attempted copy from {src} to {dst} ' '(the destination path has existing data). ' '[misc.copy_overwrite]\n' diff --git a/tests/test_util/test_util_logging.py b/tests/test_util/test_util_logging.py index aa0fc86be59..37c6c6b0015 100644 --- a/tests/test_util/test_util_logging.py +++ b/tests/test_util/test_util_logging.py @@ -9,8 +9,9 @@ import pytest from docutils import nodes +from sphinx._cli.util.errors import strip_escape_sequences from sphinx.util import logging -from sphinx.util.console import colorize, strip_colors +from sphinx.util.console import colorize from sphinx.util.logging import is_suppressed_warning, prefixed_warnings from sphinx.util.parallel import ParallelTasks @@ -115,7 +116,7 @@ def test_once_warning_log(app): logger.warning('message: %d', 1, once=True) logger.warning('message: %d', 2, once=True) - warnings = strip_colors(app.warning.getvalue()) + warnings = strip_escape_sequences(app.warning.getvalue()) assert 'WARNING: message: 1\nWARNING: message: 2\n' in warnings @@ -271,7 +272,7 @@ def test_pending_warnings(app): assert 'WARNING: message3' not in app.warning.getvalue() # actually logged as ordered - warnings = strip_colors(app.warning.getvalue()) + warnings = strip_escape_sequences(app.warning.getvalue()) assert 'WARNING: message2\nWARNING: message3' in warnings @@ -381,7 +382,7 @@ def test_show_warning_types(app): logger.warning('message3', type='test') logger.warning('message4', type='test', subtype='logging') - warnings = strip_colors(app.warning.getvalue()).splitlines() + warnings = strip_escape_sequences(app.warning.getvalue()).splitlines() assert warnings == [ 'WARNING: message2',