diff options
author | arcadia-devtools <arcadia-devtools@yandex-team.ru> | 2022-02-09 12:00:52 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 15:58:17 +0300 |
commit | 8e1413fed79d1e8036e65228af6c93399ccf5502 (patch) | |
tree | 502c9df7b2614d20541c7a2d39d390e9a51877cc /contrib/python/pytest/py3/_pytest/_io | |
parent | 6b813c17d56d1d05f92c61ddc347d0e4d358fe85 (diff) | |
download | ydb-8e1413fed79d1e8036e65228af6c93399ccf5502.tar.gz |
intermediate changes
ref:614ed510ddd3cdf86a8c5dbf19afd113397e0172
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/_io')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_io/__init__.py | 43 | ||||
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_io/saferepr.py | 60 | ||||
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_io/terminalwriter.py | 210 | ||||
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_io/wcwidth.py | 55 |
4 files changed, 314 insertions, 54 deletions
diff --git a/contrib/python/pytest/py3/_pytest/_io/__init__.py b/contrib/python/pytest/py3/_pytest/_io/__init__.py index f56579806c..db001e918c 100644 --- a/contrib/python/pytest/py3/_pytest/_io/__init__.py +++ b/contrib/python/pytest/py3/_pytest/_io/__init__.py @@ -1,39 +1,8 @@ -from typing import List -from typing import Sequence +from .terminalwriter import get_terminal_width +from .terminalwriter import TerminalWriter -from py.io import TerminalWriter as BaseTerminalWriter # noqa: F401 - -class TerminalWriter(BaseTerminalWriter): - def _write_source(self, lines: List[str], indents: Sequence[str] = ()) -> None: - """Write lines of source code possibly highlighted. - - Keeping this private for now because the API is clunky. We should discuss how - to evolve the terminal writer so we can have more precise color support, for example - being able to write part of a line in one color and the rest in another, and so on. - """ - if indents and len(indents) != len(lines): - raise ValueError( - "indents size ({}) should have same size as lines ({})".format( - len(indents), len(lines) - ) - ) - if not indents: - indents = [""] * len(lines) - source = "\n".join(lines) - new_lines = self._highlight(source).splitlines() - for indent, new_line in zip(indents, new_lines): - self.line(indent + new_line) - - def _highlight(self, source): - """Highlight the given source code according to the "code_highlight" option""" - if not self.hasmarkup: - return source - try: - from pygments.formatters.terminal import TerminalFormatter - from pygments.lexers.python import PythonLexer - from pygments import highlight - except ImportError: - return source - else: - return highlight(source, PythonLexer(), TerminalFormatter(bg="dark")) +__all__ = [ + "TerminalWriter", + "get_terminal_width", +] diff --git a/contrib/python/pytest/py3/_pytest/_io/saferepr.py b/contrib/python/pytest/py3/_pytest/_io/saferepr.py index 47a00de606..5eb1e08890 100644 --- a/contrib/python/pytest/py3/_pytest/_io/saferepr.py +++ b/contrib/python/pytest/py3/_pytest/_io/saferepr.py @@ -1,9 +1,12 @@ import pprint import reprlib from typing import Any +from typing import Dict +from typing import IO +from typing import Optional -def _try_repr_or_str(obj): +def _try_repr_or_str(obj: object) -> str: try: return repr(obj) except (KeyboardInterrupt, SystemExit): @@ -12,7 +15,7 @@ def _try_repr_or_str(obj): return '{}("{}")'.format(type(obj).__name__, obj) -def _format_repr_exception(exc: BaseException, obj: Any) -> str: +def _format_repr_exception(exc: BaseException, obj: object) -> str: try: exc_info = _try_repr_or_str(exc) except (KeyboardInterrupt, SystemExit): @@ -33,16 +36,15 @@ def _ellipsize(s: str, maxsize: int) -> str: class SafeRepr(reprlib.Repr): - """subclass of repr.Repr that limits the resulting size of repr() - and includes information on exceptions raised during the call. - """ + """repr.Repr that limits the resulting size of repr() and includes + information on exceptions raised during the call.""" def __init__(self, maxsize: int) -> None: super().__init__() self.maxstring = maxsize self.maxsize = maxsize - def repr(self, x: Any) -> str: + def repr(self, x: object) -> str: try: s = super().repr(x) except (KeyboardInterrupt, SystemExit): @@ -51,7 +53,7 @@ class SafeRepr(reprlib.Repr): s = _format_repr_exception(exc, x) return _ellipsize(s, self.maxsize) - def repr_instance(self, x: Any, level: int) -> str: + def repr_instance(self, x: object, level: int) -> str: try: s = repr(x) except (KeyboardInterrupt, SystemExit): @@ -61,8 +63,9 @@ class SafeRepr(reprlib.Repr): return _ellipsize(s, self.maxsize) -def safeformat(obj: Any) -> str: - """return a pretty printed string for the given object. +def safeformat(obj: object) -> str: + """Return a pretty printed string for the given object. + Failing __repr__ functions of user instances will be represented with a short exception info. """ @@ -72,12 +75,15 @@ def safeformat(obj: Any) -> str: return _format_repr_exception(exc, obj) -def saferepr(obj: Any, maxsize: int = 240) -> str: - """return a size-limited safe repr-string for the given object. +def saferepr(obj: object, maxsize: int = 240) -> str: + """Return a size-limited safe repr-string for the given object. + Failing __repr__ functions of user instances will be represented with a short exception info and 'saferepr' generally takes - care to never raise exceptions itself. This function is a wrapper - around the Repr/reprlib functionality of the standard 2.6 lib. + care to never raise exceptions itself. + + This function is a wrapper around the Repr/reprlib functionality of the + standard 2.6 lib. """ return SafeRepr(maxsize).repr(obj) @@ -85,19 +91,39 @@ def saferepr(obj: Any, maxsize: int = 240) -> str: class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): """PrettyPrinter that always dispatches (regardless of width).""" - def _format(self, object, stream, indent, allowance, context, level): - p = self._dispatch.get(type(object).__repr__, None) + def _format( + self, + object: object, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, Any], + level: int, + ) -> None: + # Type ignored because _dispatch is private. + p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined] objid = id(object) if objid in context or p is None: - return super()._format(object, stream, indent, allowance, context, level) + # Type ignored because _format is private. + super()._format( # type: ignore[misc] + object, stream, indent, allowance, context, level, + ) + return context[objid] = 1 p(self, object, stream, indent, allowance, context, level + 1) del context[objid] -def _pformat_dispatch(object, indent=1, width=80, depth=None, *, compact=False): +def _pformat_dispatch( + object: object, + indent: int = 1, + width: int = 80, + depth: Optional[int] = None, + *, + compact: bool = False, +) -> str: return AlwaysDispatchingPrettyPrinter( indent=indent, width=width, depth=depth, compact=compact ).pformat(object) diff --git a/contrib/python/pytest/py3/_pytest/_io/terminalwriter.py b/contrib/python/pytest/py3/_pytest/_io/terminalwriter.py new file mode 100644 index 0000000000..8edf4cd75f --- /dev/null +++ b/contrib/python/pytest/py3/_pytest/_io/terminalwriter.py @@ -0,0 +1,210 @@ +"""Helper functions for writing to terminals and files.""" +import os +import shutil +import sys +from typing import Optional +from typing import Sequence +from typing import TextIO + +from .wcwidth import wcswidth +from _pytest.compat import final + + +# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. + + +def get_terminal_width() -> int: + width, _ = shutil.get_terminal_size(fallback=(80, 24)) + + # The Windows get_terminal_size may be bogus, let's sanify a bit. + if width < 40: + width = 80 + + return width + + +def should_do_markup(file: TextIO) -> bool: + if os.environ.get("PY_COLORS") == "1": + return True + if os.environ.get("PY_COLORS") == "0": + return False + if "NO_COLOR" in os.environ: + return False + if "FORCE_COLOR" in os.environ: + return True + return ( + hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" + ) + + +@final +class TerminalWriter: + _esctable = dict( + black=30, + red=31, + green=32, + yellow=33, + blue=34, + purple=35, + cyan=36, + white=37, + Black=40, + Red=41, + Green=42, + Yellow=43, + Blue=44, + Purple=45, + Cyan=46, + White=47, + bold=1, + light=2, + blink=5, + invert=7, + ) + + def __init__(self, file: Optional[TextIO] = None) -> None: + if file is None: + file = sys.stdout + if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": + try: + import colorama + except ImportError: + pass + else: + file = colorama.AnsiToWin32(file).stream + assert file is not None + self._file = file + self.hasmarkup = should_do_markup(file) + self._current_line = "" + self._terminal_width: Optional[int] = None + self.code_highlight = True + + @property + def fullwidth(self) -> int: + if self._terminal_width is not None: + return self._terminal_width + return get_terminal_width() + + @fullwidth.setter + def fullwidth(self, value: int) -> None: + self._terminal_width = value + + @property + def width_of_current_line(self) -> int: + """Return an estimate of the width so far in the current line.""" + return wcswidth(self._current_line) + + def markup(self, text: str, **markup: bool) -> str: + for name in markup: + if name not in self._esctable: + raise ValueError(f"unknown markup: {name!r}") + if self.hasmarkup: + esc = [self._esctable[name] for name, on in markup.items() if on] + if esc: + text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m" + return text + + def sep( + self, + sepchar: str, + title: Optional[str] = None, + fullwidth: Optional[int] = None, + **markup: bool, + ) -> None: + if fullwidth is None: + fullwidth = self.fullwidth + # The goal is to have the line be as long as possible + # under the condition that len(line) <= fullwidth. + if sys.platform == "win32": + # If we print in the last column on windows we are on a + # new line but there is no way to verify/neutralize this + # (we may not know the exact line width). + # So let's be defensive to avoid empty lines in the output. + fullwidth -= 1 + if title is not None: + # we want 2 + 2*len(fill) + len(title) <= fullwidth + # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth + # 2*len(sepchar)*N <= fullwidth - len(title) - 2 + # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) + N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1) + fill = sepchar * N + line = f"{fill} {title} {fill}" + else: + # we want len(sepchar)*N <= fullwidth + # i.e. N <= fullwidth // len(sepchar) + line = sepchar * (fullwidth // len(sepchar)) + # In some situations there is room for an extra sepchar at the right, + # in particular if we consider that with a sepchar like "_ " the + # trailing space is not important at the end of the line. + if len(line) + len(sepchar.rstrip()) <= fullwidth: + line += sepchar.rstrip() + + self.line(line, **markup) + + def write(self, msg: str, *, flush: bool = False, **markup: bool) -> None: + if msg: + current_line = msg.rsplit("\n", 1)[-1] + if "\n" in msg: + self._current_line = current_line + else: + self._current_line += current_line + + msg = self.markup(msg, **markup) + + try: + self._file.write(msg) + except UnicodeEncodeError: + # Some environments don't support printing general Unicode + # strings, due to misconfiguration or otherwise; in that case, + # print the string escaped to ASCII. + # When the Unicode situation improves we should consider + # letting the error propagate instead of masking it (see #7475 + # for one brief attempt). + msg = msg.encode("unicode-escape").decode("ascii") + self._file.write(msg) + + if flush: + self.flush() + + def line(self, s: str = "", **markup: bool) -> None: + self.write(s, **markup) + self.write("\n") + + def flush(self) -> None: + self._file.flush() + + def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> None: + """Write lines of source code possibly highlighted. + + Keeping this private for now because the API is clunky. We should discuss how + to evolve the terminal writer so we can have more precise color support, for example + being able to write part of a line in one color and the rest in another, and so on. + """ + if indents and len(indents) != len(lines): + raise ValueError( + "indents size ({}) should have same size as lines ({})".format( + len(indents), len(lines) + ) + ) + if not indents: + indents = [""] * len(lines) + source = "\n".join(lines) + new_lines = self._highlight(source).splitlines() + for indent, new_line in zip(indents, new_lines): + self.line(indent + new_line) + + def _highlight(self, source: str) -> str: + """Highlight the given source code if we have markup support.""" + if not self.hasmarkup or not self.code_highlight: + return source + try: + from pygments.formatters.terminal import TerminalFormatter + from pygments.lexers.python import PythonLexer + from pygments import highlight + except ImportError: + return source + else: + highlighted: str = highlight( + source, PythonLexer(), TerminalFormatter(bg="dark") + ) + return highlighted diff --git a/contrib/python/pytest/py3/_pytest/_io/wcwidth.py b/contrib/python/pytest/py3/_pytest/_io/wcwidth.py new file mode 100644 index 0000000000..e5c7bf4d86 --- /dev/null +++ b/contrib/python/pytest/py3/_pytest/_io/wcwidth.py @@ -0,0 +1,55 @@ +import unicodedata +from functools import lru_cache + + +@lru_cache(100) +def wcwidth(c: str) -> int: + """Determine how many columns are needed to display a character in a terminal. + + Returns -1 if the character is not printable. + Returns 0, 1 or 2 for other characters. + """ + o = ord(c) + + # ASCII fast path. + if 0x20 <= o < 0x07F: + return 1 + + # Some Cf/Zp/Zl characters which should be zero-width. + if ( + o == 0x0000 + or 0x200B <= o <= 0x200F + or 0x2028 <= o <= 0x202E + or 0x2060 <= o <= 0x2063 + ): + return 0 + + category = unicodedata.category(c) + + # Control characters. + if category == "Cc": + return -1 + + # Combining characters with zero width. + if category in ("Me", "Mn"): + return 0 + + # Full/Wide east asian characters. + if unicodedata.east_asian_width(c) in ("F", "W"): + return 2 + + return 1 + + +def wcswidth(s: str) -> int: + """Determine how many columns are needed to display a string in a terminal. + + Returns -1 if the string contains non-printable characters. + """ + width = 0 + for c in unicodedata.normalize("NFC", s): + wc = wcwidth(c) + if wc < 0: + return -1 + width += wc + return width |