Merge pull request #12443 from bluetech/pygments-refactor

terminalwriter: small refactor of pygments code, improve usage errors
This commit is contained in:
Ran Benita 2024-06-10 14:13:46 +03:00 committed by GitHub
commit f3946dce07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 52 deletions

View File

@ -8,11 +8,17 @@ from typing import Literal
from typing import Optional from typing import Optional
from typing import Sequence from typing import Sequence
from typing import TextIO from typing import TextIO
from typing import TYPE_CHECKING
from ..compat import assert_never from ..compat import assert_never
from .wcwidth import wcswidth from .wcwidth import wcswidth
if TYPE_CHECKING:
from pygments.formatter import Formatter
from pygments.lexer import Lexer
# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. # This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
@ -194,58 +200,76 @@ class TerminalWriter:
for indent, new_line in zip(indents, new_lines): for indent, new_line in zip(indents, new_lines):
self.line(indent + new_line) self.line(indent + new_line)
def _highlight( def _get_pygments_lexer(
self, source: str, lexer: Literal["diff", "python"] = "python" self, lexer: Literal["python", "diff"]
) -> str: ) -> Optional["Lexer"]:
"""Highlight the given source if we have markup support.""" try:
if lexer == "python":
from pygments.lexers.python import PythonLexer
return PythonLexer()
elif lexer == "diff":
from pygments.lexers.diff import DiffLexer
return DiffLexer()
else:
assert_never(lexer)
except ModuleNotFoundError:
return None
def _get_pygments_formatter(self) -> Optional["Formatter"]:
try:
import pygments.util
except ModuleNotFoundError:
return None
from _pytest.config.exceptions import UsageError from _pytest.config.exceptions import UsageError
if not source or not self.hasmarkup or not self.code_highlight: theme = os.getenv("PYTEST_THEME")
return source theme_mode = os.getenv("PYTEST_THEME_MODE", "dark")
try: try:
from pygments.formatters.terminal import TerminalFormatter from pygments.formatters.terminal import TerminalFormatter
if lexer == "python": return TerminalFormatter(bg=theme_mode, style=theme)
from pygments.lexers.python import PythonLexer as Lexer
elif lexer == "diff":
from pygments.lexers.diff import DiffLexer as Lexer
else:
assert_never(lexer)
from pygments import highlight
import pygments.util
except ImportError:
return source
else:
try:
highlighted: str = highlight(
source,
Lexer(),
TerminalFormatter(
bg=os.getenv("PYTEST_THEME_MODE", "dark"),
style=os.getenv("PYTEST_THEME"),
),
)
# pygments terminal formatter may add a newline when there wasn't one.
# We don't want this, remove.
if highlighted[-1] == "\n" and source[-1] != "\n":
highlighted = highlighted[:-1]
# Some lexers will not set the initial color explicitly except pygments.util.ClassNotFound as e:
# which may lead to the previous color being propagated to the raise UsageError(
# start of the expression, so reset first. f"PYTEST_THEME environment variable has an invalid value: '{theme}'. "
return "\x1b[0m" + highlighted "Hint: See available pygments styles with `pygmentize -L styles`."
except pygments.util.ClassNotFound as e: ) from e
raise UsageError( except pygments.util.OptionError as e:
"PYTEST_THEME environment variable had an invalid value: '{}'. " raise UsageError(
"Only valid pygment styles are allowed.".format( f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. "
os.getenv("PYTEST_THEME") "The allowed values are 'dark' (default) and 'light'."
) ) from e
) from e
except pygments.util.OptionError as e: def _highlight(
raise UsageError( self, source: str, lexer: Literal["diff", "python"] = "python"
"PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " ) -> str:
"The only allowed values are 'dark' and 'light'.".format( """Highlight the given source if we have markup support."""
os.getenv("PYTEST_THEME_MODE") if not source or not self.hasmarkup or not self.code_highlight:
) return source
) from e
pygments_lexer = self._get_pygments_lexer(lexer)
if pygments_lexer is None:
return source
pygments_formatter = self._get_pygments_formatter()
if pygments_formatter is None:
return source
from pygments import highlight
highlighted: str = highlight(source, pygments_lexer, pygments_formatter)
# pygments terminal formatter may add a newline when there wasn't one.
# We don't want this, remove.
if highlighted[-1] == "\n" and source[-1] != "\n":
highlighted = highlighted[:-1]
# Some lexers will not set the initial color explicitly
# which may lead to the previous color being propagated to the
# start of the expression, so reset first.
highlighted = "\x1b[0m" + highlighted
return highlighted

View File

@ -2609,8 +2609,8 @@ class TestCodeHighlight:
monkeypatch.setenv("PYTEST_THEME", "invalid") monkeypatch.setenv("PYTEST_THEME", "invalid")
result = pytester.runpytest_subprocess("--color=yes") result = pytester.runpytest_subprocess("--color=yes")
result.stderr.fnmatch_lines( result.stderr.fnmatch_lines(
"ERROR: PYTEST_THEME environment variable had an invalid value: 'invalid'. " "ERROR: PYTEST_THEME environment variable has an invalid value: 'invalid'. "
"Only valid pygment styles are allowed." "Hint: See available pygments styles with `pygmentize -L styles`."
) )
def test_code_highlight_invalid_theme_mode( def test_code_highlight_invalid_theme_mode(
@ -2625,8 +2625,8 @@ class TestCodeHighlight:
monkeypatch.setenv("PYTEST_THEME_MODE", "invalid") monkeypatch.setenv("PYTEST_THEME_MODE", "invalid")
result = pytester.runpytest_subprocess("--color=yes") result = pytester.runpytest_subprocess("--color=yes")
result.stderr.fnmatch_lines( result.stderr.fnmatch_lines(
"ERROR: PYTEST_THEME_MODE environment variable had an invalid value: 'invalid'. " "ERROR: PYTEST_THEME_MODE environment variable has an invalid value: 'invalid'. "
"The only allowed values are 'dark' and 'light'." "The allowed values are 'dark' (default) and 'light'."
) )