|
8 | 8 | from typing import Optional
|
9 | 9 | from typing import Sequence
|
10 | 10 | from typing import TextIO
|
| 11 | +from typing import TYPE_CHECKING |
11 | 12 |
|
12 | 13 | from ..compat import assert_never
|
13 | 14 | from .wcwidth import wcswidth
|
14 | 15 |
|
15 | 16 |
|
| 17 | +if TYPE_CHECKING: |
| 18 | + from pygments.formatter import Formatter |
| 19 | + from pygments.lexer import Lexer |
| 20 | + |
| 21 | + |
16 | 22 | # This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
|
17 | 23 |
|
18 | 24 |
|
@@ -194,58 +200,76 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No
|
194 | 200 | for indent, new_line in zip(indents, new_lines):
|
195 | 201 | self.line(indent + new_line)
|
196 | 202 |
|
| 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 | + |
197 | 247 | def _highlight(
|
198 | 248 | self, source: str, lexer: Literal["diff", "python"] = "python"
|
199 | 249 | ) -> str:
|
200 | 250 | """Highlight the given source if we have markup support."""
|
201 |
| - from _pytest.config.exceptions import UsageError |
202 |
| - |
203 | 251 | if not source or not self.hasmarkup or not self.code_highlight:
|
204 | 252 | return source
|
205 | 253 |
|
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 |
208 | 257 |
|
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: |
218 | 260 | 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 |
0 commit comments