Skip to content

Commit

Permalink
Merge pull request #12443 from bluetech/pygments-refactor
Browse files Browse the repository at this point in the history
terminalwriter: small refactor of pygments code, improve usage errors
  • Loading branch information
bluetech committed Jun 10, 2024
2 parents 3d91e42 + 7ef9da1 commit f3946dc
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 50 deletions.
116 changes: 70 additions & 46 deletions src/_pytest/_io/terminalwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
from typing import Optional
from typing import Sequence
from typing import TextIO
from typing import TYPE_CHECKING

from ..compat import assert_never
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.


Expand Down Expand Up @@ -194,58 +200,76 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No
for indent, new_line in zip(indents, new_lines):
self.line(indent + new_line)

def _get_pygments_lexer(
self, lexer: Literal["python", "diff"]
) -> Optional["Lexer"]:
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

theme = os.getenv("PYTEST_THEME")
theme_mode = os.getenv("PYTEST_THEME_MODE", "dark")

try:
from pygments.formatters.terminal import TerminalFormatter

return TerminalFormatter(bg=theme_mode, style=theme)

except pygments.util.ClassNotFound as e:
raise UsageError(
f"PYTEST_THEME environment variable has an invalid value: '{theme}'. "
"Hint: See available pygments styles with `pygmentize -L styles`."
) from e
except pygments.util.OptionError as e:
raise UsageError(
f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. "
"The allowed values are 'dark' (default) and 'light'."
) from e

def _highlight(
self, source: str, lexer: Literal["diff", "python"] = "python"
) -> str:
"""Highlight the given source if we have markup support."""
from _pytest.config.exceptions import UsageError

if not source or not self.hasmarkup or not self.code_highlight:
return source

try:
from pygments.formatters.terminal import TerminalFormatter
pygments_lexer = self._get_pygments_lexer(lexer)
if pygments_lexer is None:
return source

if lexer == "python":
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:
pygments_formatter = self._get_pygments_formatter()
if pygments_formatter is None:
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
# which may lead to the previous color being propagated to the
# start of the expression, so reset first.
return "\x1b[0m" + highlighted
except pygments.util.ClassNotFound as e:
raise UsageError(
"PYTEST_THEME environment variable had an invalid value: '{}'. "
"Only valid pygment styles are allowed.".format(
os.getenv("PYTEST_THEME")
)
) from e
except pygments.util.OptionError as e:
raise UsageError(
"PYTEST_THEME_MODE environment variable had an invalid value: '{}'. "
"The only allowed values are 'dark' and 'light'.".format(
os.getenv("PYTEST_THEME_MODE")
)
) from e

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
8 changes: 4 additions & 4 deletions testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2609,8 +2609,8 @@ def test_foo():
monkeypatch.setenv("PYTEST_THEME", "invalid")
result = pytester.runpytest_subprocess("--color=yes")
result.stderr.fnmatch_lines(
"ERROR: PYTEST_THEME environment variable had an invalid value: 'invalid'. "
"Only valid pygment styles are allowed."
"ERROR: PYTEST_THEME environment variable has an invalid value: 'invalid'. "
"Hint: See available pygments styles with `pygmentize -L styles`."
)

def test_code_highlight_invalid_theme_mode(
Expand All @@ -2625,8 +2625,8 @@ def test_foo():
monkeypatch.setenv("PYTEST_THEME_MODE", "invalid")
result = pytester.runpytest_subprocess("--color=yes")
result.stderr.fnmatch_lines(
"ERROR: PYTEST_THEME_MODE environment variable had an invalid value: 'invalid'. "
"The only allowed values are 'dark' and 'light'."
"ERROR: PYTEST_THEME_MODE environment variable has an invalid value: 'invalid'. "
"The allowed values are 'dark' (default) and 'light'."
)


Expand Down

0 comments on commit f3946dc

Please sign in to comment.