diff --git a/CHANGES.rst b/CHANGES.rst index f59fdd81f7c..18c4c4e59dc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,9 @@ Deprecated the public properties :attr:`!sphinx.testing.util.SphinxTestApp.status` and :attr:`!sphinx.testing.util.SphinxTestApp.warning` instead. Patch by Bénédikt Tran. +* tests: :func:`!sphinx.testing.util.strip_escseq` is deprecated in favor of + :func:`!sphinx.util.console.strip_colors`. + Patch by Bénédikt Tran. Features added -------------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 882ab36fdf0..1476ce8473e 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -22,6 +22,11 @@ The following is a list of deprecated interfaces. - Removed - Alternatives + * - ``sphinx.testing.util.strip_escseq`` + - 7.3 + - 9.0 + - ``sphinx.util.console.strip_colors`` + * - Old-style Makefiles in ``sphinx-quickstart`` and the :option:`!-M`, :option:`!-m`, :option:`!--no-use-make-mode`, and :option:`!--use-make-mode` options diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index d850469a15a..55f68a395fa 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -6,7 +6,6 @@ import contextlib import os -import re import sys import warnings from io import StringIO @@ -20,12 +19,13 @@ import sphinx.application import sphinx.locale import sphinx.pycode +from sphinx.util.console import strip_colors from sphinx.util.docutils import additional_nodes if TYPE_CHECKING: from collections.abc import Mapping from pathlib import Path - from typing import Any + from typing import Any, Final from docutils.nodes import Node @@ -224,10 +224,6 @@ def build(self, *args: Any, **kwargs: Any) -> None: # otherwise, we can use built cache -def strip_escseq(text: str) -> str: - return re.sub('\x1b.*?m', '', text) - - def _clean_up_global_state() -> None: # clean up Docutils global state directives._directives.clear() # type: ignore[attr-defined] @@ -244,3 +240,20 @@ def _clean_up_global_state() -> None: # clean up autodoc global state sphinx.pycode.ModuleAnalyzer.cache.clear() + + +_DEPRECATED_OBJECTS: Final[dict[str, tuple[object, str, tuple[int, int]]]] = { + 'strip_escseq': (strip_colors, 'sphinx.util.console.strip_colors', (9, 0)), +} + + +def __getattr__(name: str) -> Any: + if name not in _DEPRECATED_OBJECTS: + msg = f'module {__name__!r} has no attribute {name!r}' + raise AttributeError(msg) + + from sphinx.deprecation import _deprecation_warning + + deprecated_object, canonical_name, remove = _DEPRECATED_OBJECTS[name] + _deprecation_warning(__name__, name, canonical_name, remove=remove) + return deprecated_object diff --git a/sphinx/util/console.py b/sphinx/util/console.py index c958073299d..8a5fe3d51fc 100644 --- a/sphinx/util/console.py +++ b/sphinx/util/console.py @@ -6,6 +6,10 @@ import re import shutil import sys +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Final try: # check if colorama is installed to support color on Windows @@ -23,6 +27,8 @@ \dK # ANSI Erase in Line )""", re.VERBOSE | re.ASCII) +_ansi_color_re: Final[re.Pattern[str]] = re.compile('\x1b.*?m') + codes: dict[str, str] = {} @@ -93,7 +99,7 @@ def escseq(name: str) -> str: def strip_colors(s: str) -> str: - return re.compile('\x1b.*?m').sub('', s) + return _ansi_color_re.sub('', s) def _strip_escape_sequences(s: str) -> str: diff --git a/tests/test_application.py b/tests/test_application.py index e3999866309..1fc49d61042 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -13,8 +13,9 @@ import sphinx.application from sphinx.errors import ExtensionError -from sphinx.testing.util import SphinxTestApp, strip_escseq +from sphinx.testing.util import SphinxTestApp from sphinx.util import logging +from sphinx.util.console import strip_colors if TYPE_CHECKING: import os @@ -79,13 +80,13 @@ def test_emit_with_nonascii_name_node(app, status, warning): def test_extensions(app, status, warning): app.setup_extension('shutil') - warning = strip_escseq(warning.getvalue()) + warning = strip_colors(warning.getvalue()) assert "extension 'shutil' has no setup() function" in warning def test_extension_in_blacklist(app, status, warning): app.setup_extension('sphinxjp.themecore') - msg = strip_escseq(warning.getvalue()) + msg = strip_colors(warning.getvalue()) assert msg.startswith("WARNING: the extension 'sphinxjp.themecore' was") diff --git a/tests/test_builders/test_build_linkcheck.py b/tests/test_builders/test_build_linkcheck.py index 4ac29a66797..3eb58b3190f 100644 --- a/tests/test_builders/test_build_linkcheck.py +++ b/tests/test_builders/test_build_linkcheck.py @@ -25,8 +25,8 @@ RateLimit, ) from sphinx.deprecation import RemovedInSphinx80Warning -from sphinx.testing.util import strip_escseq from sphinx.util import requests +from sphinx.util.console import strip_colors from tests.utils import CERT_FILE, http_server @@ -588,7 +588,7 @@ def test_linkcheck_allowed_redirects(app, warning): } assert ("index.rst:3: WARNING: redirect http://localhost:7777/path2 - with Found to " - "http://localhost:7777/?redirected=1\n" in strip_escseq(warning.getvalue())) + "http://localhost:7777/?redirected=1\n" in strip_colors(warning.getvalue())) assert len(warning.getvalue().splitlines()) == 1 @@ -785,7 +785,7 @@ def test_too_many_requests_retry_after_int_delay(app, capsys, status): "info": "", } rate_limit_log = "-rate limited- http://localhost:7777/ | sleeping...\n" - assert rate_limit_log in strip_escseq(status.getvalue()) + assert rate_limit_log in strip_colors(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 852d7647a43..1db4e051f47 100644 --- a/tests/test_builders/test_build_warnings.py +++ b/tests/test_builders/test_build_warnings.py @@ -4,7 +4,7 @@ import pytest -from sphinx.testing.util import strip_escseq +from sphinx.util.console import strip_colors ENV_WARNINGS = """\ {root}/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ @@ -42,7 +42,7 @@ def _check_warnings(expected_warnings: str, warning: str) -> None: - warnings = strip_escseq(re.sub(re.escape(os.sep) + '{1,2}', '/', warning)) + warnings = strip_colors(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' diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py index 8aaf4679b24..e54339c1220 100644 --- a/tests/test_intl/test_intl.py +++ b/tests/test_intl/test_intl.py @@ -15,7 +15,8 @@ from docutils import nodes from sphinx import locale -from sphinx.testing.util import assert_node, etree_parse, strip_escseq +from sphinx.testing.util import assert_node, etree_parse +from sphinx.util.console import strip_colors from sphinx.util.nodes import NodeMatcher _CATALOG_LOCALE = 'xx' @@ -1593,7 +1594,7 @@ def test_image_glob_intl_using_figure_language_filename(app): def getwarning(warnings): - return strip_escseq(warnings.getvalue().replace(os.sep, '/')) + return strip_colors(warnings.getvalue().replace(os.sep, '/')) @pytest.mark.sphinx('html', testroot='basic', diff --git a/tests/test_util/test_util_display.py b/tests/test_util/test_util_display.py index ab995d15ea3..a18fa1e9445 100644 --- a/tests/test_util/test_util_display.py +++ b/tests/test_util/test_util_display.py @@ -2,8 +2,8 @@ import pytest -from sphinx.testing.util import strip_escseq from sphinx.util import logging +from sphinx.util.console import strip_colors from sphinx.util.display import ( SkipProgressMessage, display_chunk, @@ -28,7 +28,7 @@ def test_status_iterator_length_0(app, status, warning): status.seek(0) status.truncate(0) yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... ')) - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing ... hello sphinx world \n' in output assert yields == ['hello', 'sphinx', 'world'] @@ -43,7 +43,7 @@ def test_status_iterator_verbosity_0(app, status, warning, monkeypatch): status.truncate(0) yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... ', length=3, verbosity=0)) - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing ... [ 33%] hello\r' in output assert 'testing ... [ 67%] sphinx\r' in output assert 'testing ... [100%] world\r\n' in output @@ -60,7 +60,7 @@ def test_status_iterator_verbosity_1(app, status, warning, monkeypatch): status.truncate(0) yields = list(status_iterator(['hello', 'sphinx', 'world'], 'testing ... ', length=3, verbosity=1)) - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing ... [ 33%] hello\n' in output assert 'testing ... [ 67%] sphinx\n' in output assert 'testing ... [100%] world\n\n' in output @@ -75,14 +75,14 @@ def test_progress_message(app, status, warning): with progress_message('testing'): logger.info('blah ', nonl=True) - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing... blah done\n' in output # skipping case with progress_message('testing'): raise SkipProgressMessage('Reason: %s', 'error') # NoQA: EM101 - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing... skipped\nReason: error\n' in output # error case @@ -92,7 +92,7 @@ def test_progress_message(app, status, warning): except Exception: pass - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing... failed\n' in output # decorator @@ -101,5 +101,5 @@ def func(): logger.info('in func ', nonl=True) func() - output = strip_escseq(status.getvalue()) + output = strip_colors(status.getvalue()) assert 'testing... in func done\n' in output diff --git a/tests/test_util/test_util_logging.py b/tests/test_util/test_util_logging.py index 8c621880313..4ee548a7a3c 100644 --- a/tests/test_util/test_util_logging.py +++ b/tests/test_util/test_util_logging.py @@ -8,7 +8,6 @@ from docutils import nodes from sphinx.errors import SphinxWarning -from sphinx.testing.util import strip_escseq from sphinx.util import logging, osutil from sphinx.util.console import colorize, strip_colors from sphinx.util.logging import is_suppressed_warning, prefixed_warnings @@ -110,7 +109,7 @@ def test_once_warning_log(app, status, warning): logger.warning('message: %d', 1, once=True) logger.warning('message: %d', 2, once=True) - assert 'WARNING: message: 1\nWARNING: message: 2\n' in strip_escseq(warning.getvalue()) + assert 'WARNING: message: 1\nWARNING: message: 2\n' in strip_colors(warning.getvalue()) def test_is_suppressed_warning(): @@ -278,7 +277,7 @@ def test_pending_warnings(app, status, warning): assert 'WARNING: message3' not in warning.getvalue() # actually logged as ordered - assert 'WARNING: message2\nWARNING: message3' in strip_escseq(warning.getvalue()) + assert 'WARNING: message2\nWARNING: message3' in strip_colors(warning.getvalue()) def test_colored_logs(app, status, warning):