Skip to content

Commit f3946dc

Browse files
authored
Merge pull request #12443 from bluetech/pygments-refactor
terminalwriter: small refactor of pygments code, improve usage errors
2 parents 3d91e42 + 7ef9da1 commit f3946dc

File tree

2 files changed

+74
-50
lines changed

2 files changed

+74
-50
lines changed

src/_pytest/_io/terminalwriter.py

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@
88
from typing import Optional
99
from typing import Sequence
1010
from typing import TextIO
11+
from typing import TYPE_CHECKING
1112

1213
from ..compat import assert_never
1314
from .wcwidth import wcswidth
1415

1516

17+
if TYPE_CHECKING:
18+
from pygments.formatter import Formatter
19+
from pygments.lexer import Lexer
20+
21+
1622
# This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
1723

1824

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

203+
def _get_pygments_lexer(
204+
self, lexer: Literal["python", "diff"]
205+
) -> Optional["Lexer"]:
206+
try:
207+
if lexer == "python":
208+
from pygments.lexers.python import PythonLexer
209+
210+
return PythonLexer()
211+
elif lexer == "diff":
212+
from pygments.lexers.diff import DiffLexer
213+
214+
return DiffLexer()
215+
else:
216+
assert_never(lexer)
217+
except ModuleNotFoundError:
218+
return None
219+
220+
def _get_pygments_formatter(self) -> Optional["Formatter"]:
221+
try:
222+
import pygments.util
223+
except ModuleNotFoundError:
224+
return None
225+
226+
from _pytest.config.exceptions import UsageError
227+
228+
theme = os.getenv("PYTEST_THEME")
229+
theme_mode = os.getenv("PYTEST_THEME_MODE", "dark")
230+
231+
try:
232+
from pygments.formatters.terminal import TerminalFormatter
233+
234+
return TerminalFormatter(bg=theme_mode, style=theme)
235+
236+
except pygments.util.ClassNotFound as e:
237+
raise UsageError(
238+
f"PYTEST_THEME environment variable has an invalid value: '{theme}'. "
239+
"Hint: See available pygments styles with `pygmentize -L styles`."
240+
) from e
241+
except pygments.util.OptionError as e:
242+
raise UsageError(
243+
f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. "
244+
"The allowed values are 'dark' (default) and 'light'."
245+
) from e
246+
197247
def _highlight(
198248
self, source: str, lexer: Literal["diff", "python"] = "python"
199249
) -> str:
200250
"""Highlight the given source if we have markup support."""
201-
from _pytest.config.exceptions import UsageError
202-
203251
if not source or not self.hasmarkup or not self.code_highlight:
204252
return source
205253

206-
try:
207-
from pygments.formatters.terminal import TerminalFormatter
254+
pygments_lexer = self._get_pygments_lexer(lexer)
255+
if pygments_lexer is None:
256+
return source
208257

209-
if lexer == "python":
210-
from pygments.lexers.python import PythonLexer as Lexer
211-
elif lexer == "diff":
212-
from pygments.lexers.diff import DiffLexer as Lexer
213-
else:
214-
assert_never(lexer)
215-
from pygments import highlight
216-
import pygments.util
217-
except ImportError:
258+
pygments_formatter = self._get_pygments_formatter()
259+
if pygments_formatter is None:
218260
return source
219-
else:
220-
try:
221-
highlighted: str = highlight(
222-
source,
223-
Lexer(),
224-
TerminalFormatter(
225-
bg=os.getenv("PYTEST_THEME_MODE", "dark"),
226-
style=os.getenv("PYTEST_THEME"),
227-
),
228-
)
229-
# pygments terminal formatter may add a newline when there wasn't one.
230-
# We don't want this, remove.
231-
if highlighted[-1] == "\n" and source[-1] != "\n":
232-
highlighted = highlighted[:-1]
233-
234-
# Some lexers will not set the initial color explicitly
235-
# which may lead to the previous color being propagated to the
236-
# start of the expression, so reset first.
237-
return "\x1b[0m" + highlighted
238-
except pygments.util.ClassNotFound as e:
239-
raise UsageError(
240-
"PYTEST_THEME environment variable had an invalid value: '{}'. "
241-
"Only valid pygment styles are allowed.".format(
242-
os.getenv("PYTEST_THEME")
243-
)
244-
) from e
245-
except pygments.util.OptionError as e:
246-
raise UsageError(
247-
"PYTEST_THEME_MODE environment variable had an invalid value: '{}'. "
248-
"The only allowed values are 'dark' and 'light'.".format(
249-
os.getenv("PYTEST_THEME_MODE")
250-
)
251-
) from e
261+
262+
from pygments import highlight
263+
264+
highlighted: str = highlight(source, pygments_lexer, pygments_formatter)
265+
# pygments terminal formatter may add a newline when there wasn't one.
266+
# We don't want this, remove.
267+
if highlighted[-1] == "\n" and source[-1] != "\n":
268+
highlighted = highlighted[:-1]
269+
270+
# Some lexers will not set the initial color explicitly
271+
# which may lead to the previous color being propagated to the
272+
# start of the expression, so reset first.
273+
highlighted = "\x1b[0m" + highlighted
274+
275+
return highlighted

testing/test_terminal.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2609,8 +2609,8 @@ def test_foo():
26092609
monkeypatch.setenv("PYTEST_THEME", "invalid")
26102610
result = pytester.runpytest_subprocess("--color=yes")
26112611
result.stderr.fnmatch_lines(
2612-
"ERROR: PYTEST_THEME environment variable had an invalid value: 'invalid'. "
2613-
"Only valid pygment styles are allowed."
2612+
"ERROR: PYTEST_THEME environment variable has an invalid value: 'invalid'. "
2613+
"Hint: See available pygments styles with `pygmentize -L styles`."
26142614
)
26152615

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

26322632

0 commit comments

Comments
 (0)