summaryrefslogtreecommitdiff
path: root/src/pip/_vendor/rich
diff options
context:
space:
mode:
authorPradyun Gedam <pradyunsg@users.noreply.github.com>2022-01-21 15:50:17 +0000
committerPradyun Gedam <pradyunsg@users.noreply.github.com>2022-01-25 08:51:14 +0000
commit1c61502ce126283132b1f47f3fb48276bfcefd95 (patch)
tree0e1c99da1b759656ee50eb2f3d83d1e26ac92280 /src/pip/_vendor/rich
parentd706df7e101b175e786c08aef4b08648d84cdfd0 (diff)
downloadpip-1c61502ce126283132b1f47f3fb48276bfcefd95.tar.gz
Upgrade rich to 11.0.0
Diffstat (limited to 'src/pip/_vendor/rich')
-rw-r--r--src/pip/_vendor/rich/__main__.py27
-rw-r--r--src/pip/_vendor/rich/_windows.py5
-rw-r--r--src/pip/_vendor/rich/cells.py40
-rw-r--r--src/pip/_vendor/rich/console.py54
-rw-r--r--src/pip/_vendor/rich/default_styles.py24
-rw-r--r--src/pip/_vendor/rich/file_proxy.py2
-rw-r--r--src/pip/_vendor/rich/jupyter.py12
-rw-r--r--src/pip/_vendor/rich/live.py60
-rw-r--r--src/pip/_vendor/rich/live_render.py10
-rw-r--r--src/pip/_vendor/rich/logging.py11
-rw-r--r--src/pip/_vendor/rich/markup.py2
-rw-r--r--src/pip/_vendor/rich/padding.py10
-rw-r--r--src/pip/_vendor/rich/pager.py2
-rw-r--r--src/pip/_vendor/rich/pretty.py316
-rw-r--r--src/pip/_vendor/rich/segment.py155
-rw-r--r--src/pip/_vendor/rich/syntax.py42
-rw-r--r--src/pip/_vendor/rich/table.py195
-rw-r--r--src/pip/_vendor/rich/text.py138
-rw-r--r--src/pip/_vendor/rich/traceback.py24
19 files changed, 740 insertions, 389 deletions
diff --git a/src/pip/_vendor/rich/__main__.py b/src/pip/_vendor/rich/__main__.py
index 60d03efba..8692d37e0 100644
--- a/src/pip/_vendor/rich/__main__.py
+++ b/src/pip/_vendor/rich/__main__.py
@@ -4,13 +4,7 @@ from time import process_time
from pip._vendor.rich import box
from pip._vendor.rich.color import Color
-from pip._vendor.rich.console import (
- Console,
- ConsoleOptions,
- Group,
- RenderResult,
- RenderableType,
-)
+from pip._vendor.rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
from pip._vendor.rich.markdown import Markdown
from pip._vendor.rich.measure import Measurement
from pip._vendor.rich.pretty import Pretty
@@ -222,7 +216,10 @@ if __name__ == "__main__": # pragma: no cover
test_card = make_test_card()
# Print once to warm cache
+ start = process_time()
console.print(test_card)
+ pre_cache_taken = round((process_time() - start) * 1000.0, 1)
+
console.file = io.StringIO()
start = process_time()
@@ -234,7 +231,8 @@ if __name__ == "__main__": # pragma: no cover
for line in text.splitlines(True):
print(line, end="")
- print(f"rendered in {taken}ms")
+ print(f"rendered in {pre_cache_taken}ms (cold cache)")
+ print(f"rendered in {taken}ms (warm cache)")
from pip._vendor.rich.panel import Panel
@@ -243,13 +241,10 @@ if __name__ == "__main__": # pragma: no cover
sponsor_message = Table.grid(padding=1)
sponsor_message.add_column(style="green", justify="right")
sponsor_message.add_column(no_wrap=True)
+
sponsor_message.add_row(
- "Sponsor me",
- "[u blue link=https://github.com/sponsors/willmcgugan]https://github.com/sponsors/willmcgugan",
- )
- sponsor_message.add_row(
- "Buy me a :coffee:",
- "[u blue link=https://ko-fi.com/willmcgugan]https://ko-fi.com/willmcgugan",
+ "Buy devs a :coffee:",
+ "[u blue link=https://ko-fi.com/textualize]https://ko-fi.com/textualize",
)
sponsor_message.add_row(
"Twitter",
@@ -261,9 +256,9 @@ if __name__ == "__main__": # pragma: no cover
intro_message = Text.from_markup(
"""\
-It takes a lot of time to develop Rich and to provide support.
+We hope you enjoy using Rich!
-Consider supporting my work via Github Sponsors (ask your company / organization), or buy me a coffee to say thanks.
+Rich is maintained with :heart: by [link=https://www.textualize.io]Textualize.io[/]
- Will McGugan"""
)
diff --git a/src/pip/_vendor/rich/_windows.py b/src/pip/_vendor/rich/_windows.py
index 0fdbc4539..ca3a680d3 100644
--- a/src/pip/_vendor/rich/_windows.py
+++ b/src/pip/_vendor/rich/_windows.py
@@ -1,5 +1,4 @@
import sys
-
from dataclasses import dataclass
@@ -15,8 +14,7 @@ class WindowsConsoleFeatures:
try:
import ctypes
- from ctypes import wintypes
- from ctypes import LibraryLoader
+ from ctypes import LibraryLoader, wintypes
if sys.platform == "win32":
windll = LibraryLoader(ctypes.WinDLL)
@@ -30,7 +28,6 @@ except (AttributeError, ImportError, ValueError):
features = WindowsConsoleFeatures()
return features
-
else:
STDOUT = -11
diff --git a/src/pip/_vendor/rich/cells.py b/src/pip/_vendor/rich/cells.py
index b02f4b868..e824ea2a6 100644
--- a/src/pip/_vendor/rich/cells.py
+++ b/src/pip/_vendor/rich/cells.py
@@ -1,9 +1,13 @@
from functools import lru_cache
+import re
from typing import Dict, List
from ._cell_widths import CELL_WIDTHS
from ._lru_cache import LRUCache
+# Regex to match sequence of the most common character ranges
+_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match
+
def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int:
"""Get the number of cells required to display text.
@@ -12,16 +16,19 @@ def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int:
text (str): Text to display.
Returns:
- int: Number of cells required to display the text.
+ int: Get the number of cells required to display text.
"""
- cached_result = _cache.get(text, None)
- if cached_result is not None:
- return cached_result
-
- _get_size = get_character_cell_size
- total_size = sum(_get_size(character) for character in text)
- if len(text) <= 64:
- _cache[text] = total_size
+
+ if _is_single_cell_widths(text):
+ return len(text)
+ else:
+ cached_result = _cache.get(text, None)
+ if cached_result is not None:
+ return cached_result
+ _get_size = get_character_cell_size
+ total_size = sum(_get_size(character) for character in text)
+ if len(text) <= 64:
+ _cache[text] = total_size
return total_size
@@ -35,12 +42,10 @@ def get_character_cell_size(character: str) -> int:
Returns:
int: Number of cells (0, 1 or 2) occupied by that character.
"""
-
- codepoint = ord(character)
- if 127 > codepoint > 31:
- # Shortcut for ascii
+ if _is_single_cell_widths(character):
return 1
- return _get_codepoint_cell_size(codepoint)
+
+ return _get_codepoint_cell_size(ord(character))
@lru_cache(maxsize=4096)
@@ -74,6 +79,13 @@ def _get_codepoint_cell_size(codepoint: int) -> int:
def set_cell_size(text: str, total: int) -> str:
"""Set the length of a string to fit within given number of cells."""
+
+ if _is_single_cell_widths(text):
+ size = len(text)
+ if size < total:
+ return text + " " * (total - size)
+ return text[:total]
+
if not total:
return ""
cell_size = cell_len(text)
diff --git a/src/pip/_vendor/rich/console.py b/src/pip/_vendor/rich/console.py
index ee07d7ad7..27e722760 100644
--- a/src/pip/_vendor/rich/console.py
+++ b/src/pip/_vendor/rich/console.py
@@ -1,7 +1,6 @@
import inspect
import os
import platform
-import shutil
import sys
import threading
from abc import ABC, abstractmethod
@@ -12,8 +11,9 @@ from getpass import getpass
from html import escape
from inspect import isclass
from itertools import islice
+from threading import RLock
from time import monotonic
-from types import FrameType, TracebackType, ModuleType
+from types import FrameType, ModuleType, TracebackType
from typing import (
IO,
TYPE_CHECKING,
@@ -25,7 +25,6 @@ from typing import (
Mapping,
NamedTuple,
Optional,
- Set,
TextIO,
Tuple,
Type,
@@ -212,6 +211,19 @@ class ConsoleOptions:
options.min_width = options.max_width = max(0, width)
return options
+ def update_height(self, height: int) -> "ConsoleOptions":
+ """Update the height, and return a copy.
+
+ Args:
+ height (int): New height
+
+ Returns:
+ ~ConsoleOptions: New Console options instance.
+ """
+ options = self.copy()
+ options.max_height = options.height = height
+ return options
+
def update_dimensions(self, width: int, height: int) -> "ConsoleOptions":
"""Update the width and height, and return a copy.
@@ -224,8 +236,7 @@ class ConsoleOptions:
"""
options = self.copy()
options.min_width = options.max_width = max(0, width)
- options.height = height
- options.max_height = height
+ options.height = options.max_height = height
return options
@@ -247,11 +258,12 @@ class ConsoleRenderable(Protocol):
...
+# A type that may be rendered by Console.
RenderableType = Union[ConsoleRenderable, RichCast, str]
-"""A type that may be rendered by Console."""
+
+# The result of calling a __rich_console__ method.
RenderResult = Iterable[Union[RenderableType, Segment]]
-"""The result of calling a __rich_console__ method."""
_null_highlighter = NullHighlighter()
@@ -464,9 +476,6 @@ class Group:
yield from self.renderables
-RenderGroup = Group # TODO: deprecate at some point
-
-
def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
"""A decorator that turns an iterable of renderables in to a group.
@@ -477,7 +486,7 @@ def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
def decorator(
method: Callable[..., Iterable[RenderableType]]
) -> Callable[..., Group]:
- """Convert a method that returns an iterable of renderables in to a RenderGroup."""
+ """Convert a method that returns an iterable of renderables in to a Group."""
@wraps(method)
def _replace(*args: Any, **kwargs: Any) -> Group:
@@ -489,9 +498,6 @@ def group(fit: bool = True) -> Callable[..., Callable[..., Group]]:
return decorator
-render_group = group
-
-
def _is_jupyter() -> bool: # pragma: no cover
"""Check if we're running in a Jupyter notebook."""
try:
@@ -813,12 +819,13 @@ class Console:
Args:
hook (RenderHook): Render hook instance.
"""
-
- self._render_hooks.append(hook)
+ with self._lock:
+ self._render_hooks.append(hook)
def pop_render_hook(self) -> None:
"""Pop the last renderhook from the stack."""
- self._render_hooks.pop()
+ with self._lock:
+ self._render_hooks.pop()
def __enter__(self) -> "Console":
"""Own context manager to enter buffer context."""
@@ -1495,9 +1502,8 @@ class Console:
control_codes (str): Control codes, such as those that may move the cursor.
"""
if not self.is_dumb_terminal:
- for _control in control:
- self._buffer.append(_control.segment)
- self._check_buffer()
+ with self:
+ self._buffer.extend(_control.segment for _control in control)
def out(
self,
@@ -1579,7 +1585,7 @@ class Console:
if overflow is None:
overflow = "ignore"
crop = False
-
+ render_hooks = self._render_hooks[:]
with self:
renderables = self._collect_renderables(
objects,
@@ -1590,7 +1596,7 @@ class Console:
markup=markup,
highlight=highlight,
)
- for hook in self._render_hooks:
+ for hook in render_hooks:
renderables = hook.process_renderables(renderables)
render_options = self.options.update(
justify=justify,
@@ -1847,6 +1853,8 @@ class Console:
if not objects:
objects = (NewLine(),)
+ render_hooks = self._render_hooks[:]
+
with self:
renderables = self._collect_renderables(
objects,
@@ -1881,7 +1889,7 @@ class Console:
link_path=link_path,
)
]
- for hook in self._render_hooks:
+ for hook in render_hooks:
renderables = hook.process_renderables(renderables)
new_segments: List[Segment] = []
extend = new_segments.extend
diff --git a/src/pip/_vendor/rich/default_styles.py b/src/pip/_vendor/rich/default_styles.py
index 355f9df85..91ab232d3 100644
--- a/src/pip/_vendor/rich/default_styles.py
+++ b/src/pip/_vendor/rich/default_styles.py
@@ -157,3 +157,27 @@ DEFAULT_STYLES: Dict[str, Style] = {
"markdown.link": Style(color="bright_blue"),
"markdown.link_url": Style(color="blue"),
}
+
+
+if __name__ == "__main__": # pragma: no cover
+ import argparse
+ import io
+
+ from pip._vendor.rich.console import Console
+ from pip._vendor.rich.table import Table
+ from pip._vendor.rich.text import Text
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--html", action="store_true", help="Export as HTML table")
+ args = parser.parse_args()
+ html: bool = args.html
+ console = Console(record=True, width=70, file=io.StringIO()) if html else Console()
+
+ table = Table("Name", "Styling")
+
+ for style_name, style in DEFAULT_STYLES.items():
+ table.add_row(Text(style_name, style=style), str(style))
+
+ console.print(table)
+ if html:
+ print(console.export_html(inline_styles=True))
diff --git a/src/pip/_vendor/rich/file_proxy.py b/src/pip/_vendor/rich/file_proxy.py
index 99a6922cb..3ec593a5a 100644
--- a/src/pip/_vendor/rich/file_proxy.py
+++ b/src/pip/_vendor/rich/file_proxy.py
@@ -44,7 +44,7 @@ class FileProxy(io.TextIOBase):
output = Text("\n").join(
self.__ansi_decoder.decode_line(line) for line in lines
)
- console.print(output, markup=False, emoji=False, highlight=False)
+ console.print(output)
return len(text)
def flush(self) -> None:
diff --git a/src/pip/_vendor/rich/jupyter.py b/src/pip/_vendor/rich/jupyter.py
index 7cdcc9cab..bedf5cb19 100644
--- a/src/pip/_vendor/rich/jupyter.py
+++ b/src/pip/_vendor/rich/jupyter.py
@@ -4,7 +4,6 @@ from . import get_console
from .segment import Segment
from .terminal_theme import DEFAULT_TERMINAL_THEME
-
JUPYTER_HTML_FORMAT = """\
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
"""
@@ -75,11 +74,16 @@ def _render_segments(segments: Iterable[Segment]) -> str:
def display(segments: Iterable[Segment], text: str) -> None:
"""Render segments to Jupyter."""
- from IPython.display import display as ipython_display
-
html = _render_segments(segments)
jupyter_renderable = JupyterRenderable(html, text)
- ipython_display(jupyter_renderable)
+ try:
+ from IPython.display import display as ipython_display
+
+ ipython_display(jupyter_renderable)
+ except ModuleNotFoundError:
+ # Handle the case where the Console has force_jupyter=True,
+ # but IPython is not installed.
+ pass
def print(*args: Any, **kwargs: Any) -> None:
diff --git a/src/pip/_vendor/rich/live.py b/src/pip/_vendor/rich/live.py
index 8097f7d2e..6db5b605f 100644
--- a/src/pip/_vendor/rich/live.py
+++ b/src/pip/_vendor/rich/live.py
@@ -130,35 +130,29 @@ class Live(JupyterMixin, RenderHook):
return
self.console.clear_live()
self._started = False
- try:
- if self.auto_refresh and self._refresh_thread is not None:
- self._refresh_thread.stop()
- # allow it to fully render on the last even if overflow
- self.vertical_overflow = "visible"
- if not self._alt_screen and not self.console.is_jupyter:
- self.refresh()
-
- finally:
- self._disable_redirect_io()
- self.console.pop_render_hook()
- if not self._alt_screen and self.console.is_terminal:
- self.console.line()
- self.console.show_cursor(True)
- if self._alt_screen:
- self.console.set_alt_screen(False)
-
- if self._refresh_thread is not None:
- self._refresh_thread.join()
- self._refresh_thread = None
- if self.transient and not self._alt_screen:
- self.console.control(self._live_render.restore_cursor())
- if self.ipy_widget is not None: # pragma: no cover
- if self.transient:
- self.ipy_widget.close()
- else:
- # jupyter last refresh must occur after console pop render hook
- # i am not sure why this is needed
- self.refresh()
+
+ if self.auto_refresh and self._refresh_thread is not None:
+ self._refresh_thread.stop()
+ self._refresh_thread = None
+ # allow it to fully render on the last even if overflow
+ self.vertical_overflow = "visible"
+ with self.console:
+ try:
+ if not self._alt_screen and not self.console.is_jupyter:
+ self.refresh()
+ finally:
+ self._disable_redirect_io()
+ self.console.pop_render_hook()
+ if not self._alt_screen and self.console.is_terminal:
+ self.console.line()
+ self.console.show_cursor(True)
+ if self._alt_screen:
+ self.console.set_alt_screen(False)
+
+ if self.transient and not self._alt_screen:
+ self.console.control(self._live_render.restore_cursor())
+ if self.ipy_widget is not None and self.transient:
+ self.ipy_widget.close() # pragma: no cover
def __enter__(self) -> "Live":
self.start(refresh=self._renderable is not None)
@@ -174,7 +168,7 @@ class Live(JupyterMixin, RenderHook):
def _enable_redirect_io(self) -> None:
"""Enable redirecting of stdout / stderr."""
- if self.console.is_terminal:
+ if self.console.is_terminal or self.console.is_jupyter:
if self._redirect_stdout and not isinstance(sys.stdout, FileProxy):
self._restore_stdout = sys.stdout
sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout))
@@ -255,11 +249,7 @@ class Live(JupyterMixin, RenderHook):
if self._alt_screen
else self._live_render.position_cursor()
)
- renderables = [
- reset,
- *renderables,
- self._live_render,
- ]
+ renderables = [reset, *renderables, self._live_render]
elif (
not self._started and not self.transient
): # if it is finished render the final output for files or dumb_terminals
diff --git a/src/pip/_vendor/rich/live_render.py b/src/pip/_vendor/rich/live_render.py
index b02fd3176..b90fbf7f3 100644
--- a/src/pip/_vendor/rich/live_render.py
+++ b/src/pip/_vendor/rich/live_render.py
@@ -84,16 +84,15 @@ class LiveRender:
) -> RenderResult:
renderable = self.renderable
- _Segment = Segment
style = console.get_style(self.style)
lines = console.render_lines(renderable, options, style=style, pad=False)
- shape = _Segment.get_shape(lines)
+ shape = Segment.get_shape(lines)
_, height = shape
if height > options.size.height:
if self.vertical_overflow == "crop":
lines = lines[: options.size.height]
- shape = _Segment.get_shape(lines)
+ shape = Segment.get_shape(lines)
elif self.vertical_overflow == "ellipsis":
lines = lines[: (options.size.height - 1)]
overflow_text = Text(
@@ -104,10 +103,11 @@ class LiveRender:
style="live.ellipsis",
)
lines.append(list(console.render(overflow_text)))
- shape = _Segment.get_shape(lines)
+ shape = Segment.get_shape(lines)
self._shape = shape
+ new_line = Segment.line()
for last, line in loop_last(lines):
yield from line
if not last:
- yield _Segment.line()
+ yield new_line
diff --git a/src/pip/_vendor/rich/logging.py b/src/pip/_vendor/rich/logging.py
index 47ca7d42d..002f1f7bf 100644
--- a/src/pip/_vendor/rich/logging.py
+++ b/src/pip/_vendor/rich/logging.py
@@ -164,12 +164,13 @@ class RichHandler(Handler):
Returns:
ConsoleRenderable: Renderable to display log message.
"""
- use_markup = (
- getattr(record, "markup") if hasattr(record, "markup") else self.markup
- )
+ use_markup = getattr(record, "markup", self.markup)
message_text = Text.from_markup(message) if use_markup else Text(message)
- if self.highlighter:
- message_text = self.highlighter(message_text)
+
+ highlighter = getattr(record, "highlighter", self.highlighter)
+ if highlighter:
+ message_text = highlighter(message_text)
+
if self.KEYWORDS:
message_text.highlight_words(self.KEYWORDS, "logging.keyword")
return message_text
diff --git a/src/pip/_vendor/rich/markup.py b/src/pip/_vendor/rich/markup.py
index ff861a354..619540202 100644
--- a/src/pip/_vendor/rich/markup.py
+++ b/src/pip/_vendor/rich/markup.py
@@ -47,7 +47,7 @@ _EscapeSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compi
def escape(
- markup: str, _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#\/].*?\])").sub
+ markup: str, _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#\/@].*?\])").sub
) -> str:
"""Escapes text so that it won't be interpreted as markup.
diff --git a/src/pip/_vendor/rich/padding.py b/src/pip/_vendor/rich/padding.py
index f024e95e0..1b2204f59 100644
--- a/src/pip/_vendor/rich/padding.py
+++ b/src/pip/_vendor/rich/padding.py
@@ -89,11 +89,13 @@ class Padding(JupyterMixin):
+ self.right,
options.max_width,
)
+ render_options = options.update_width(width - self.left - self.right)
+ if render_options.height is not None:
+ render_options = render_options.update_height(
+ height=render_options.height - self.top - self.bottom
+ )
lines = console.render_lines(
- self.renderable,
- options.update_width(width - self.left - self.right),
- style=style,
- pad=True,
+ self.renderable, render_options, style=style, pad=True
)
_Segment = Segment
diff --git a/src/pip/_vendor/rich/pager.py b/src/pip/_vendor/rich/pager.py
index ea9bdf08f..dbfb973e3 100644
--- a/src/pip/_vendor/rich/pager.py
+++ b/src/pip/_vendor/rich/pager.py
@@ -17,7 +17,7 @@ class Pager(ABC):
class SystemPager(Pager):
"""Uses the pager installed on the system."""
- def _pager(self, content: str) -> Any:
+ def _pager(self, content: str) -> Any: #  pragma: no cover
return __import__("pydoc").pager(content)
def show(self, content: str) -> None:
diff --git a/src/pip/_vendor/rich/pretty.py b/src/pip/_vendor/rich/pretty.py
index cfce487af..606ee3382 100644
--- a/src/pip/_vendor/rich/pretty.py
+++ b/src/pip/_vendor/rich/pretty.py
@@ -1,28 +1,30 @@
import builtins
+import dataclasses
+import inspect
import os
-from pip._vendor.rich.repr import RichReprResult
+import re
import sys
from array import array
-from collections import Counter, defaultdict, deque, UserDict, UserList
-import dataclasses
+from collections import Counter, UserDict, UserList, defaultdict, deque
from dataclasses import dataclass, fields, is_dataclass
from inspect import isclass
from itertools import islice
-import re
+from types import MappingProxyType
from typing import (
- DefaultDict,
TYPE_CHECKING,
Any,
Callable,
+ DefaultDict,
Dict,
Iterable,
List,
Optional,
Set,
- Union,
Tuple,
+ Union,
)
-from types import MappingProxyType
+
+from pip._vendor.rich.repr import RichReprResult
try:
import attr as _attr_module
@@ -30,7 +32,6 @@ except ImportError: # pragma: no cover
_attr_module = None # type: ignore
-from .highlighter import ReprHighlighter
from . import get_console
from ._loop import loop_last
from ._pick import pick_bool
@@ -51,9 +52,6 @@ if TYPE_CHECKING:
RenderResult,
)
-# Matches Jupyter's special methods
-_re_jupyter_repr = re.compile(f"^_repr_.+_$")
-
def _is_attr_object(obj: Any) -> bool:
"""Check if an object was created with attrs module."""
@@ -78,10 +76,74 @@ def _is_dataclass_repr(obj: object) -> bool:
# Catching all exceptions in case something is missing on a non CPython implementation
try:
return obj.__repr__.__code__.co_filename == dataclasses.__file__
- except Exception:
+ except Exception: # pragma: no coverage
return False
+def _ipy_display_hook(
+ value: Any,
+ console: Optional["Console"] = None,
+ overflow: "OverflowMethod" = "ignore",
+ crop: bool = False,
+ indent_guides: bool = False,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ expand_all: bool = False,
+) -> None:
+ from .console import ConsoleRenderable # needed here to prevent circular import
+
+ # always skip rich generated jupyter renderables or None values
+ if isinstance(value, JupyterRenderable) or value is None:
+ return
+
+ console = console or get_console()
+ if console.is_jupyter:
+ # Delegate rendering to IPython if the object (and IPython) supports it
+ # https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
+ ipython_repr_methods = [
+ "_repr_html_",
+ "_repr_markdown_",
+ "_repr_json_",
+ "_repr_latex_",
+ "_repr_jpeg_",
+ "_repr_png_",
+ "_repr_svg_",
+ "_repr_mimebundle_",
+ ]
+ for repr_method in ipython_repr_methods:
+ method = getattr(value, repr_method, None)
+ if inspect.ismethod(method):
+ # Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
+ # specifies that if they return None, then they should not be rendered
+ # by the notebook.
+ try:
+ repr_result = method()
+ except Exception:
+ continue # If the method raises, treat it as if it doesn't exist, try any others
+ if repr_result is not None:
+ return # Delegate rendering to IPython
+
+ # certain renderables should start on a new line
+ if isinstance(value, ConsoleRenderable):
+ console.line()
+
+ console.print(
+ value
+ if isinstance(value, RichRenderable)
+ else Pretty(
+ value,
+ overflow=overflow,
+ indent_guides=indent_guides,
+ max_length=max_length,
+ max_string=max_string,
+ expand_all=expand_all,
+ margin=12,
+ ),
+ crop=crop,
+ new_line_start=True,
+ )
+
+
def install(
console: Optional["Console"] = None,
overflow: "OverflowMethod" = "ignore",
@@ -106,8 +168,6 @@ def install(
"""
from pip._vendor.rich import get_console
- from .console import ConsoleRenderable # needed here to prevent circular import
-
console = console or get_console()
assert console is not None
@@ -131,37 +191,6 @@ def install(
)
builtins._ = value # type: ignore
- def ipy_display_hook(value: Any) -> None: # pragma: no cover
- assert console is not None
- # always skip rich generated jupyter renderables or None values
- if isinstance(value, JupyterRenderable) or value is None:
- return
- # on jupyter rich display, if using one of the special representations don't use rich
- if console.is_jupyter and any(
- _re_jupyter_repr.match(attr) for attr in dir(value)
- ):
- return
-
- # certain renderables should start on a new line
- if isinstance(value, ConsoleRenderable):
- console.line()
-
- console.print(
- value
- if isinstance(value, RichRenderable)
- else Pretty(
- value,
- overflow=overflow,
- indent_guides=indent_guides,
- max_length=max_length,
- max_string=max_string,
- expand_all=expand_all,
- margin=12,
- ),
- crop=crop,
- new_line_start=True,
- )
-
try: # pragma: no cover
ip = get_ipython() # type: ignore
from IPython.core.formatters import BaseFormatter
@@ -171,7 +200,15 @@ def install(
def __call__(self, value: Any) -> Any:
if self.pprint:
- return ipy_display_hook(value)
+ return _ipy_display_hook(
+ value,
+ console=get_console(),
+ overflow=overflow,
+ indent_guides=indent_guides,
+ max_length=max_length,
+ max_string=max_string,
+ expand_all=expand_all,
+ )
else:
return repr(value)
@@ -196,6 +233,7 @@ class Pretty(JupyterMixin):
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
+ max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None.
expand_all (bool, optional): Expand all containers. Defaults to False.
margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0.
insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False.
@@ -213,6 +251,7 @@ class Pretty(JupyterMixin):
indent_guides: bool = False,
max_length: Optional[int] = None,
max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
expand_all: bool = False,
margin: int = 0,
insert_line: bool = False,
@@ -226,6 +265,7 @@ class Pretty(JupyterMixin):
self.indent_guides = indent_guides
self.max_length = max_length
self.max_string = max_string
+ self.max_depth = max_depth
self.expand_all = expand_all
self.margin = margin
self.insert_line = insert_line
@@ -239,6 +279,7 @@ class Pretty(JupyterMixin):
indent_size=self.indent_size,
max_length=self.max_length,
max_string=self.max_string,
+ max_depth=self.max_depth,
expand_all=self.expand_all,
)
pretty_text = Text(
@@ -474,7 +515,10 @@ class _Line:
def traverse(
- _object: Any, max_length: Optional[int] = None, max_string: Optional[int] = None
+ _object: Any,
+ max_length: Optional[int] = None,
+ max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
) -> Node:
"""Traverse object and generate a tree.
@@ -484,6 +528,8 @@ def traverse(
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
Defaults to None.
+ max_depth (int, optional): Maximum depth of data structures, or None for no maximum.
+ Defaults to None.
Returns:
Node: The root of a tree structure which can be used to render a pretty repr.
@@ -509,11 +555,13 @@ def traverse(
push_visited = visited_ids.add
pop_visited = visited_ids.remove
- def _traverse(obj: Any, root: bool = False) -> Node:
+ def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node:
"""Walk the object depth first."""
+
obj_type = type(obj)
py_version = (sys.version_info.major, sys.version_info.minor)
children: List[Node]
+ reached_max_depth = max_depth is not None and depth >= max_depth
def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
for arg in rich_args:
@@ -554,33 +602,37 @@ def traverse(
if args:
children = []
append = children.append
- if angular:
- node = Node(
- open_brace=f"<{class_name} ",
- close_brace=">",
- children=children,
- last=root,
- separator=" ",
- )
+
+ if reached_max_depth:
+ node = Node(value_repr=f"...")
else:
- node = Node(
- open_brace=f"{class_name}(",
- close_brace=")",
- children=children,
- last=root,
- )
- for last, arg in loop_last(args):
- if isinstance(arg, tuple):
- key, child = arg
- child_node = _traverse(child)
- child_node.last = last
- child_node.key_repr = key
- child_node.key_separator = "="
- append(child_node)
+ if angular:
+ node = Node(
+ open_brace=f"<{class_name} ",
+ close_brace=">",
+ children=children,
+ last=root,
+ separator=" ",
+ )
else:
- child_node = _traverse(arg)
- child_node.last = last
- append(child_node)
+ node = Node(
+ open_brace=f"{class_name}(",
+ close_brace=")",
+ children=children,
+ last=root,
+ )
+ for last, arg in loop_last(args):
+ if isinstance(arg, tuple):
+ key, child = arg
+ child_node = _traverse(child, depth=depth + 1)
+ child_node.last = last
+ child_node.key_repr = key
+ child_node.key_separator = "="
+ append(child_node)
+ else:
+ child_node = _traverse(arg, depth=depth + 1)
+ child_node.last = last
+ append(child_node)
else:
node = Node(
value_repr=f"<{class_name}>" if angular else f"{class_name}()",
@@ -593,40 +645,43 @@ def traverse(
attr_fields = _get_attr_fields(obj)
if attr_fields:
- node = Node(
- open_brace=f"{obj.__class__.__name__}(",
- close_brace=")",
- children=children,
- last=root,
- )
+ if reached_max_depth:
+ node = Node(value_repr=f"...")
+ else:
+ node = Node(
+ open_brace=f"{obj.__class__.__name__}(",
+ close_brace=")",
+ children=children,
+ last=root,
+ )
- def iter_attrs() -> Iterable[
- Tuple[str, Any, Optional[Callable[[Any], str]]]
- ]:
- """Iterate over attr fields and values."""
- for attr in attr_fields:
- if attr.repr:
- try:
- value = getattr(obj, attr.name)
- except Exception as error:
- # Can happen, albeit rarely
- yield (attr.name, error, None)
- else:
- yield (
- attr.name,
- value,
- attr.repr if callable(attr.repr) else None,
- )
-
- for last, (name, value, repr_callable) in loop_last(iter_attrs()):
- if repr_callable:
- child_node = Node(value_repr=str(repr_callable(value)))
- else:
- child_node = _traverse(value)
- child_node.last = last
- child_node.key_repr = name
- child_node.key_separator = "="
- append(child_node)
+ def iter_attrs() -> Iterable[
+ Tuple[str, Any, Optional[Callable[[Any], str]]]
+ ]:
+ """Iterate over attr fields and values."""
+ for attr in attr_fields:
+ if attr.repr:
+ try:
+ value = getattr(obj, attr.name)
+ except Exception as error:
+ # Can happen, albeit rarely
+ yield (attr.name, error, None)
+ else:
+ yield (
+ attr.name,
+ value,
+ attr.repr if callable(attr.repr) else None,
+ )
+
+ for last, (name, value, repr_callable) in loop_last(iter_attrs()):
+ if repr_callable:
+ child_node = Node(value_repr=str(repr_callable(value)))
+ else:
+ child_node = _traverse(value, depth=depth + 1)
+ child_node.last = last
+ child_node.key_repr = name
+ child_node.key_separator = "="
+ append(child_node)
else:
node = Node(
value_repr=f"{obj.__class__.__name__}()", children=[], last=root
@@ -646,21 +701,26 @@ def traverse(
children = []
append = children.append
- node = Node(
- open_brace=f"{obj.__class__.__name__}(",
- close_brace=")",
- children=children,
- last=root,
- )
+ if reached_max_depth:
+ node = Node(value_repr=f"...")
+ else:
+ node = Node(
+ open_brace=f"{obj.__class__.__name__}(",
+ close_brace=")",
+ children=children,
+ last=root,
+ )
- for last, field in loop_last(field for field in fields(obj) if field.repr):
- child_node = _traverse(getattr(obj, field.name))
- child_node.key_repr = field.name
- child_node.last = last
- child_node.key_separator = "="
- append(child_node)
+ for last, field in loop_last(
+ field for field in fields(obj) if field.repr
+ ):
+ child_node = _traverse(getattr(obj, field.name), depth=depth + 1)
+ child_node.key_repr = field.name
+ child_node.last = last
+ child_node.key_separator = "="
+ append(child_node)
- pop_visited(obj_id)
+ pop_visited(obj_id)
elif isinstance(obj, _CONTAINERS):
for container_type in _CONTAINERS:
@@ -676,7 +736,9 @@ def traverse(
open_brace, close_brace, empty = _BRACES[obj_type](obj)
- if obj_type.__repr__ != type(obj).__repr__:
+ if reached_max_depth:
+ node = Node(value_repr=f"...", last=root)
+ elif obj_type.__repr__ != type(obj).__repr__:
node = Node(value_repr=to_repr(obj), last=root)
elif obj:
children = []
@@ -695,7 +757,7 @@ def traverse(
if max_length is not None:
iter_items = islice(iter_items, max_length)
for index, (key, child) in enumerate(iter_items):
- child_node = _traverse(child)
+ child_node = _traverse(child, depth=depth + 1)
child_node.key_repr = to_repr(key)
child_node.last = index == last_item_index
append(child_node)
@@ -704,7 +766,7 @@ def traverse(
if max_length is not None:
iter_values = islice(iter_values, max_length)
for index, child in enumerate(iter_values):
- child_node = _traverse(child)
+ child_node = _traverse(child, depth=depth + 1)
child_node.last = index == last_item_index
append(child_node)
if max_length is not None and num_items > max_length:
@@ -729,6 +791,7 @@ def pretty_repr(
indent_size: int = 4,
max_length: Optional[int] = None,
max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
expand_all: bool = False,
) -> str:
"""Prettify repr string by expanding on to new lines to fit within a given width.
@@ -741,6 +804,8 @@ def pretty_repr(
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
Defaults to None.
+ max_depth (int, optional): Maximum depth of nested data structure, or None for no depth.
+ Defaults to None.
expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
Returns:
@@ -750,7 +815,9 @@ def pretty_repr(
if isinstance(_object, Node):
node = _object
else:
- node = traverse(_object, max_length=max_length, max_string=max_string)
+ node = traverse(
+ _object, max_length=max_length, max_string=max_string, max_depth=max_depth
+ )
repr_str = node.render(
max_width=max_width, indent_size=indent_size, expand_all=expand_all
)
@@ -764,6 +831,7 @@ def pprint(
indent_guides: bool = True,
max_length: Optional[int] = None,
max_string: Optional[int] = None,
+ max_depth: Optional[int] = None,
expand_all: bool = False,
) -> None:
"""A convenience function for pretty printing.
@@ -774,6 +842,7 @@ def pprint(
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
+ max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None.
indent_guides (bool, optional): Enable indentation guides. Defaults to True.
expand_all (bool, optional): Expand all containers. Defaults to False.
"""
@@ -783,6 +852,7 @@ def pprint(
_object,
max_length=max_length,
max_string=max_string,
+ max_depth=max_depth,
indent_guides=indent_guides,
expand_all=expand_all,
overflow="ignore",
diff --git a/src/pip/_vendor/rich/segment.py b/src/pip/_vendor/rich/segment.py
index 869b61010..94ca73076 100644
--- a/src/pip/_vendor/rich/segment.py
+++ b/src/pip/_vendor/rich/segment.py
@@ -12,10 +12,16 @@ from typing import (
Optional,
Sequence,
Tuple,
+ Type,
Union,
)
-from .cells import cell_len, get_character_cell_size, set_cell_size
+from .cells import (
+ _is_single_cell_widths,
+ cell_len,
+ get_character_cell_size,
+ set_cell_size,
+)
from .repr import Result, rich_repr
from .style import Style
@@ -97,19 +103,14 @@ class Segment(NamedTuple):
text, style, control = segment
_Segment = Segment
- if cut >= segment.cell_length:
- return segment, _Segment("", style, control)
- if len(text) == segment.cell_length:
- # Fast path with all 1 cell characters
- return (
- _Segment(text[:cut], style, control),
- _Segment(text[cut:], style, control),
- )
+ cell_length = segment.cell_length
+ if cut >= cell_length:
+ return segment, _Segment("", style, control)
cell_size = get_character_cell_size
- pos = int((cut / segment.cell_length) * len(text))
+ pos = int((cut / cell_length) * len(text))
before = text[:pos]
cell_pos = cell_len(before)
@@ -143,6 +144,17 @@ class Segment(NamedTuple):
Returns:
Tuple[Segment, Segment]: Two segments.
"""
+ text, style, control = self
+
+ if _is_single_cell_widths(text):
+ # Fast path with all 1 cell characters
+ if cut >= len(text):
+ return self, Segment("", style, control)
+ return (
+ Segment(text[:cut], style, control),
+ Segment(text[cut:], style, control),
+ )
+
return self._split_cells(self, cut)
@classmethod
@@ -341,7 +353,8 @@ class Segment(NamedTuple):
Returns:
int: The length of the line.
"""
- return sum(segment.cell_length for segment in line)
+ _cell_len = cell_len
+ return sum(_cell_len(segment.text) for segment in line)
@classmethod
def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]:
@@ -372,34 +385,117 @@ class Segment(NamedTuple):
lines (List[List[Segment]]): A list of lines.
width (int): Desired width.
height (int, optional): Desired height or None for no change.
- style (Style, optional): Style of any padding added. Defaults to None.
+ style (Style, optional): Style of any padding added.
new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
Returns:
- List[List[Segment]]: New list of lines that fits width x height.
+ List[List[Segment]]: New list of lines.
"""
- if height is None:
- height = len(lines)
- shaped_lines: List[List[Segment]] = []
- pad_line = (
- [Segment(" " * width, style), Segment("\n")]
- if new_lines
- else [Segment(" " * width, style)]
+ _height = height or len(lines)
+
+ blank = (
+ [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)]
)
- append = shaped_lines.append
adjust_line_length = cls.adjust_line_length
- line: Optional[List[Segment]]
- iter_lines = iter(lines)
- for _ in range(height):
- line = next(iter_lines, None)
- if line is None:
- append(pad_line)
- else:
- append(adjust_line_length(line, width, style=style))
+ shaped_lines = lines[:_height]
+ shaped_lines[:] = [
+ adjust_line_length(line, width, style=style) for line in lines
+ ]
+ if len(shaped_lines) < _height:
+ shaped_lines.extend([blank] * (_height - len(shaped_lines)))
return shaped_lines
@classmethod
+ def align_top(
+ cls: Type["Segment"],
+ lines: List[List["Segment"]],
+ width: int,
+ height: int,
+ style: Style,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Aligns lines to top (adds extra lines to bottom as required).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style): Style of any padding added.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ extra_lines = height - len(lines)
+ if not extra_lines:
+ return lines[:]
+ lines = lines[:height]
+ blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
+ lines = lines + [[blank]] * extra_lines
+ return lines
+
+ @classmethod
+ def align_bottom(
+ cls: Type["Segment"],
+ lines: List[List["Segment"]],
+ width: int,
+ height: int,
+ style: Style,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Aligns render to bottom (adds extra lines above as required).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style): Style of any padding added. Defaults to None.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ extra_lines = height - len(lines)
+ if not extra_lines:
+ return lines[:]
+ lines = lines[:height]
+ blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
+ lines = [[blank]] * extra_lines + lines
+ return lines
+
+ @classmethod
+ def align_middle(
+ cls: Type["Segment"],
+ lines: List[List["Segment"]],
+ width: int,
+ height: int,
+ style: Style,
+ new_lines: bool = False,
+ ) -> List[List["Segment"]]:
+ """Aligns lines to middle (adds extra lines to above and below as required).
+
+ Args:
+ lines (List[List[Segment]]): A list of lines.
+ width (int): Desired width.
+ height (int, optional): Desired height or None for no change.
+ style (Style): Style of any padding added.
+ new_lines (bool, optional): Padded lines should include "\n". Defaults to False.
+
+ Returns:
+ List[List[Segment]]: New list of lines.
+ """
+ extra_lines = height - len(lines)
+ if not extra_lines:
+ return lines[:]
+ lines = lines[:height]
+ blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style)
+ top_lines = extra_lines // 2
+ bottom_lines = extra_lines - top_lines
+ lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines
+ return lines
+
+ @classmethod
def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
"""Simplify an iterable of segments by combining contiguous segments with the same style.
@@ -492,6 +588,7 @@ class Segment(NamedTuple):
"""
split_segments: List["Segment"] = []
add_segment = split_segments.append
+
iter_cuts = iter(cuts)
while True:
diff --git a/src/pip/_vendor/rich/syntax.py b/src/pip/_vendor/rich/syntax.py
index 254963e91..58cc1037f 100644
--- a/src/pip/_vendor/rich/syntax.py
+++ b/src/pip/_vendor/rich/syntax.py
@@ -5,6 +5,7 @@ import textwrap
from abc import ABC, abstractmethod
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union
+from pip._vendor.pygments.lexer import Lexer
from pip._vendor.pygments.lexers import get_lexer_by_name, guess_lexer_for_filename
from pip._vendor.pygments.style import Style as PygmentsStyle
from pip._vendor.pygments.styles import get_style_by_name
@@ -194,7 +195,7 @@ class Syntax(JupyterMixin):
Args:
code (str): Code to highlight.
- lexer_name (str): Lexer to use (see https://pygments.org/docs/lexers/)
+ lexer (Lexer | str): Lexer to use (see https://pygments.org/docs/lexers/)
theme (str, optional): Color theme, aka Pygments style (see https://pygments.org/docs/styles/#getting-a-list-of-available-styles). Defaults to "monokai".
dedent (bool, optional): Enable stripping of initial whitespace. Defaults to False.
line_numbers (bool, optional): Enable rendering of line numbers. Defaults to False.
@@ -226,7 +227,7 @@ class Syntax(JupyterMixin):
def __init__(
self,
code: str,
- lexer_name: str,
+ lexer: Union[Lexer, str],
*,
theme: Union[str, SyntaxTheme] = DEFAULT_THEME,
dedent: bool = False,
@@ -241,7 +242,7 @@ class Syntax(JupyterMixin):
indent_guides: bool = False,
) -> None:
self.code = code
- self.lexer_name = lexer_name
+ self._lexer = lexer
self.dedent = dedent
self.line_numbers = line_numbers
self.start_line = start_line
@@ -348,6 +349,25 @@ class Syntax(JupyterMixin):
style = self._theme.get_style_for_token(token_type)
return style.color
+ @property
+ def lexer(self) -> Optional[Lexer]:
+ """The lexer for this syntax, or None if no lexer was found.
+
+ Tries to find the lexer by name if a string was passed to the constructor.
+ """
+
+ if isinstance(self._lexer, Lexer):
+ return self._lexer
+ try:
+ return get_lexer_by_name(
+ self._lexer,
+ stripnl=False,
+ ensurenl=True,
+ tabsize=self.tab_size,
+ )
+ except ClassNotFound:
+ return None
+
def highlight(
self, code: str, line_range: Optional[Tuple[int, int]] = None
) -> Text:
@@ -373,14 +393,10 @@ class Syntax(JupyterMixin):
no_wrap=not self.word_wrap,
)
_get_theme_style = self._theme.get_style_for_token
- try:
- lexer = get_lexer_by_name(
- self.lexer_name,
- stripnl=False,
- ensurenl=True,
- tabsize=self.tab_size,
- )
- except ClassNotFound:
+
+ lexer = self.lexer
+
+ if lexer is None:
text.append(code)
else:
if line_range:
@@ -390,6 +406,8 @@ class Syntax(JupyterMixin):
def line_tokenize() -> Iterable[Tuple[Any, str]]:
"""Split tokens to one per line."""
+ assert lexer
+
for token_type, token in lexer.get_tokens(code):
while token:
line_token, new_line, token = token.partition("\n")
@@ -698,7 +716,7 @@ if __name__ == "__main__": # pragma: no cover
code = sys.stdin.read()
syntax = Syntax(
code=code,
- lexer_name=args.lexer_name,
+ lexer=args.lexer_name,
line_numbers=args.line_numbers,
word_wrap=args.word_wrap,
theme=args.theme,
diff --git a/src/pip/_vendor/rich/table.py b/src/pip/_vendor/rich/table.py
index 554ff4013..da4386085 100644
--- a/src/pip/_vendor/rich/table.py
+++ b/src/pip/_vendor/rich/table.py
@@ -1,7 +1,7 @@
from dataclasses import dataclass, field, replace
from typing import (
- Dict,
TYPE_CHECKING,
+ Dict,
Iterable,
List,
NamedTuple,
@@ -15,6 +15,7 @@ from . import box, errors
from ._loop import loop_first_last, loop_last
from ._pick import pick_bool
from ._ratio import ratio_distribute, ratio_reduce
+from .align import VerticalAlignMethod
from .jupyter import JupyterMixin
from .measure import Measurement
from .padding import Padding, PaddingDimensions
@@ -56,6 +57,9 @@ class Column:
justify: "JustifyMethod" = "left"
"""str: How to justify text within the column ("left", "center", "right", or "full")"""
+ vertical: "VerticalAlignMethod" = "top"
+ """str: How to vertically align content ("top", "middle", or "bottom")"""
+
overflow: "OverflowMethod" = "ellipsis"
"""str: Overflow method."""
@@ -112,6 +116,8 @@ class _Cell(NamedTuple):
"""Style to apply to cell."""
renderable: "RenderableType"
"""Cell renderable."""
+ vertical: VerticalAlignMethod
+ """Cell vertical alignment."""
class Table(JupyterMixin):
@@ -335,6 +341,7 @@ class Table(JupyterMixin):
footer_style: Optional[StyleType] = None,
style: Optional[StyleType] = None,
justify: "JustifyMethod" = "left",
+ vertical: "VerticalAlignMethod" = "top",
overflow: "OverflowMethod" = "ellipsis",
width: Optional[int] = None,
min_width: Optional[int] = None,
@@ -353,6 +360,7 @@ class Table(JupyterMixin):
footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
+ vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
@@ -369,6 +377,7 @@ class Table(JupyterMixin):
footer_style=footer_style or "",
style=style or "",
justify=justify,
+ vertical=vertical,
overflow=overflow,
width=width,
min_width=min_width,
@@ -636,10 +645,18 @@ class Table(JupyterMixin):
if any_padding:
_Padding = Padding
for first, last, (style, renderable) in loop_first_last(raw_cells):
- yield _Cell(style, _Padding(renderable, get_padding(first, last)))
+ yield _Cell(
+ style,
+ _Padding(renderable, get_padding(first, last)),
+ getattr(renderable, "vertical", None) or column.vertical,
+ )
else:
for (style, renderable) in raw_cells:
- yield _Cell(style, renderable)
+ yield _Cell(
+ style,
+ renderable,
+ getattr(renderable, "vertical", None) or column.vertical,
+ )
def _get_padding_width(self, column_index: int) -> int:
"""Get extra width from padding."""
@@ -770,18 +787,45 @@ class Table(JupyterMixin):
overflow=column.overflow,
height=None,
)
- cell_style = table_style + row_style + get_style(cell.style)
lines = console.render_lines(
- cell.renderable, render_options, style=cell_style
+ cell.renderable,
+ render_options,
+ style=get_style(cell.style) + row_style,
)
max_height = max(max_height, len(lines))
cells.append(lines)
+ row_height = max(len(cell) for cell in cells)
+
+ def align_cell(
+ cell: List[List[Segment]],
+ vertical: "VerticalAlignMethod",
+ width: int,
+ style: Style,
+ ) -> List[List[Segment]]:
+ if header_row:
+ vertical = "bottom"
+ elif footer_row:
+ vertical = "top"
+
+ if vertical == "top":
+ return _Segment.align_top(cell, width, row_height, style)
+ elif vertical == "middle":
+ return _Segment.align_middle(cell, width, row_height, style)
+ return _Segment.align_bottom(cell, width, row_height, style)
+
cells[:] = [
_Segment.set_shape(
- _cell, width, max_height, style=table_style + row_style
+ align_cell(
+ cell,
+ _cell.vertical,
+ width,
+ get_style(_cell.style) + row_style,
+ ),
+ width,
+ max_height,
)
- for width, _cell in zip(widths, cells)
+ for width, _cell, cell, column in zip(widths, row_cell, cells, columns)
]
if _box:
@@ -848,72 +892,77 @@ if __name__ == "__main__": # pragma: no cover
from pip._vendor.rich.highlighter import ReprHighlighter
from pip._vendor.rich.table import Table as Table
- table = Table(
- title="Star Wars Movies",
- caption="Rich example table",
- caption_justify="right",
- )
+ from ._timer import timer
+
+ with timer("Table render"):
+ table = Table(
+ title="Star Wars Movies",
+ caption="Rich example table",
+ caption_justify="right",
+ )
- table.add_column("Released", header_style="bright_cyan", style="cyan", no_wrap=True)
- table.add_column("Title", style="magenta")
- table.add_column("Box Office", justify="right", style="green")
+ table.add_column(
+ "Released", header_style="bright_cyan", style="cyan", no_wrap=True
+ )
+ table.add_column("Title", style="magenta")
+ table.add_column("Box Office", justify="right", style="green")
- table.add_row(
- "Dec 20, 2019",
- "Star Wars: The Rise of Skywalker",
- "$952,110,690",
- )
- table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
- table.add_row(
- "Dec 15, 2017",
- "Star Wars Ep. V111: The Last Jedi",
- "$1,332,539,889",
- style="on black",
- end_section=True,
- )
- table.add_row(
- "Dec 16, 2016",
- "Rogue One: A Star Wars Story",
- "$1,332,439,889",
- )
+ table.add_row(
+ "Dec 20, 2019",
+ "Star Wars: The Rise of Skywalker",
+ "$952,110,690",
+ )
+ table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
+ table.add_row(
+ "Dec 15, 2017",
+ "Star Wars Ep. V111: The Last Jedi",
+ "$1,332,539,889",
+ style="on black",
+ end_section=True,
+ )
+ table.add_row(
+ "Dec 16, 2016",
+ "Rogue One: A Star Wars Story",
+ "$1,332,439,889",
+ )
- def header(text: str) -> None:
- console.print()
- console.rule(highlight(text))
- console.print()
-
- console = Console()
- highlight = ReprHighlighter()
- header("Example Table")
- console.print(table, justify="center")
-
- table.expand = True
- header("expand=True")
- console.print(table)
-
- table.width = 50
- header("width=50")
-
- console.print(table, justify="center")
-
- table.width = None
- table.expand = False
- table.row_styles = ["dim", "none"]
- header("row_styles=['dim', 'none']")
-
- console.print(table, justify="center")
-
- table.width = None
- table.expand = False
- table.row_styles = ["dim", "none"]
- table.leading = 1
- header("leading=1, row_styles=['dim', 'none']")
- console.print(table, justify="center")
-
- table.width = None
- table.expand = False
- table.row_styles = ["dim", "none"]
- table.show_lines = True
- table.leading = 0
- header("show_lines=True, row_styles=['dim', 'none']")
- console.print(table, justify="center")
+ def header(text: str) -> None:
+ console.print()
+ console.rule(highlight(text))
+ console.print()
+
+ console = Console()
+ highlight = ReprHighlighter()
+ header("Example Table")
+ console.print(table, justify="center")
+
+ table.expand = True
+ header("expand=True")
+ console.print(table)
+
+ table.width = 50
+ header("width=50")
+
+ console.print(table, justify="center")
+
+ table.width = None
+ table.expand = False
+ table.row_styles = ["dim", "none"]
+ header("row_styles=['dim', 'none']")
+
+ console.print(table, justify="center")
+
+ table.width = None
+ table.expand = False
+ table.row_styles = ["dim", "none"]
+ table.leading = 1
+ header("leading=1, row_styles=['dim', 'none']")
+ console.print(table, justify="center")
+
+ table.width = None
+ table.expand = False
+ table.row_styles = ["dim", "none"]
+ table.show_lines = True
+ table.leading = 0
+ header("show_lines=True, row_styles=['dim', 'none']")
+ console.print(table, justify="center")
diff --git a/src/pip/_vendor/rich/text.py b/src/pip/_vendor/rich/text.py
index 5c9742d7f..ea12c09d7 100644
--- a/src/pip/_vendor/rich/text.py
+++ b/src/pip/_vendor/rich/text.py
@@ -1,7 +1,7 @@
import re
from functools import partial, reduce
from math import gcd
-from operator import attrgetter, itemgetter
+from operator import itemgetter
from pip._vendor.rich.emoji import EmojiVariant
from typing import (
TYPE_CHECKING,
@@ -213,6 +213,36 @@ class Text(JupyterMixin):
"""Get the number of cells required to render this text."""
return cell_len(self.plain)
+ @property
+ def markup(self) -> str:
+ """Get console markup to render this Text.
+
+ Returns:
+ str: A string potentially creating markup tags.
+ """
+ from .markup import escape
+
+ output: List[str] = []
+
+ plain = self.plain
+ markup_spans = [
+ (0, False, self.style),
+ *((span.start, False, span.style) for span in self._spans),
+ *((span.end, True, span.style) for span in self._spans),
+ (len(plain), True, self.style),
+ ]
+ markup_spans.sort(key=itemgetter(0, 1))
+ position = 0
+ append = output.append
+ for offset, closing, style in markup_spans:
+ if offset > position:
+ append(escape(plain[position:offset]))
+ position = offset
+ if style:
+ append(f"[/{style}]" if closing else f"[{style}]")
+ markup = "".join(output)
+ return markup
+
@classmethod
def from_markup(
cls,
@@ -243,6 +273,44 @@ class Text(JupyterMixin):
return rendered_text
@classmethod
+ def from_ansi(
+ cls,
+ text: str,
+ *,
+ style: Union[str, Style] = "",
+ justify: Optional["JustifyMethod"] = None,
+ overflow: Optional["OverflowMethod"] = None,
+ no_wrap: Optional[bool] = None,
+ end: str = "\n",
+ tab_size: Optional[int] = 8,
+ ) -> "Text":
+ """Create a Text object from a string containing ANSI escape codes.
+
+ Args:
+ text (str): A string containing escape codes.
+ style (Union[str, Style], optional): Base style for text. Defaults to "".
+ justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
+ overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
+ no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None.
+ end (str, optional): Character to end text with. Defaults to "\\\\n".
+ tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8.
+ """
+ from .ansi import AnsiDecoder
+
+ joiner = Text(
+ "\n",
+ justify=justify,
+ overflow=overflow,
+ no_wrap=no_wrap,
+ end=end,
+ tab_size=tab_size,
+ style=style,
+ )
+ decoder = AnsiDecoder()
+ result = joiner.join(line for line in decoder.decode(text))
+ return result
+
+ @classmethod
def styled(
cls,
text: str,
@@ -965,6 +1033,7 @@ class Text(JupyterMixin):
Lines: New RichText instances between offsets.
"""
_offsets = list(offsets)
+
if not _offsets:
return Lines([self.copy()])
@@ -988,33 +1057,49 @@ class Text(JupyterMixin):
)
if not self._spans:
return new_lines
- order = {span: span_index for span_index, span in enumerate(self._spans)}
- span_stack = sorted(self._spans, key=attrgetter("start"), reverse=True)
- pop = span_stack.pop
- push = span_stack.append
+ _line_appends = [line._spans.append for line in new_lines._lines]
+ line_count = len(line_ranges)
_Span = Span
- get_order = order.__getitem__
-
- for line, (start, end) in zip(new_lines, line_ranges):
- if not span_stack:
- break
- append_span = line._spans.append
- position = len(span_stack) - 1
- while span_stack[position].start < end:
- span = pop(position)
- add_span, remaining_span = span.split(end)
- if remaining_span:
- push(remaining_span)
- order[remaining_span] = order[span]
- span_start, span_end, span_style = add_span
- line_span = _Span(span_start - start, span_end - start, span_style)
- order[line_span] = order[span]
- append_span(line_span)
- position -= 1
- if position < 0 or not span_stack:
- break # pragma: no cover
- line._spans.sort(key=get_order)
+
+ for span_start, span_end, style in self._spans:
+
+ lower_bound = 0
+ upper_bound = line_count
+ start_line_no = (lower_bound + upper_bound) // 2
+
+ while True:
+ line_start, line_end = line_ranges[start_line_no]
+ if span_start < line_start:
+ upper_bound = start_line_no - 1
+ elif span_start > line_end:
+ lower_bound = start_line_no + 1
+ else:
+ break
+ start_line_no = (lower_bound + upper_bound) // 2
+
+ if span_end < line_end:
+ end_line_no = start_line_no
+ else:
+ end_line_no = lower_bound = start_line_no
+ upper_bound = line_count
+
+ while True:
+ line_start, line_end = line_ranges[end_line_no]
+ if span_end < line_start:
+ upper_bound = end_line_no - 1
+ elif span_end > line_end:
+ lower_bound = end_line_no + 1
+ else:
+ break
+ end_line_no = (lower_bound + upper_bound) // 2
+
+ for line_no in range(start_line_no, end_line_no + 1):
+ line_start, line_end = line_ranges[line_no]
+ new_start = max(0, span_start - line_start)
+ new_end = min(span_end - line_start, line_end - line_start)
+ if new_end > new_start:
+ _line_appends[line_no](_Span(new_start, new_end, style))
return new_lines
@@ -1179,6 +1264,7 @@ if __name__ == "__main__": # pragma: no cover
text.highlight_words(["ipsum"], "italic")
console = Console()
+
console.rule("justify='left'")
console.print(text, style="red")
console.print()
diff --git a/src/pip/_vendor/rich/traceback.py b/src/pip/_vendor/rich/traceback.py
index dbb9f1f70..66a39ebab 100644
--- a/src/pip/_vendor/rich/traceback.py
+++ b/src/pip/_vendor/rich/traceback.py
@@ -16,13 +16,7 @@ from pip._vendor.pygments.token import Token
from . import pretty
from ._loop import loop_first, loop_last
from .columns import Columns
-from .console import (
- Console,
- ConsoleOptions,
- ConsoleRenderable,
- RenderResult,
- group,
-)
+from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group
from .constrain import Constrain
from .highlighter import RegexHighlighter, ReprHighlighter
from .panel import Panel
@@ -135,15 +129,15 @@ def install(
)
try: # pragma: no cover
- # if wihin ipython, use customized traceback
+ # if within ipython, use customized traceback
ip = get_ipython() # type: ignore
ipy_excepthook_closure(ip)
- return sys.excepthook # type: ignore # more strict signature that mypy can't interpret
+ return sys.excepthook
except Exception:
# otherwise use default system hook
old_excepthook = sys.excepthook
sys.excepthook = excepthook
- return old_excepthook # type: ignore # more strict signature that mypy can't interpret
+ return old_excepthook
@dataclass
@@ -246,6 +240,9 @@ class Traceback:
self.suppress: Sequence[str] = []
for suppress_entity in suppress:
if not isinstance(suppress_entity, str):
+ assert (
+ suppress_entity.__file__ is not None
+ ), f"{suppress_entity!r} must be a module with '__file__' attribute"
path = os.path.dirname(suppress_entity.__file__)
else:
path = suppress_entity
@@ -440,7 +437,8 @@ class Traceback:
"scope.equals": token_style(Operator),
"scope.key": token_style(Name),
"scope.key.special": token_style(Name.Constant) + Style(dim=True),
- }
+ },
+ inherit=False,
)
highlighter = ReprHighlighter()
@@ -450,7 +448,7 @@ class Traceback:
self._render_stack(stack),
title="[traceback.title]Traceback [dim](most recent call last)",
style=background_style,
- border_style="traceback.border.syntax_error",
+ border_style="traceback.border",
expand=True,
padding=(0, 1),
)
@@ -463,7 +461,7 @@ class Traceback:
Panel(
self._render_syntax_error(stack.syntax_error),
style=background_style,
- border_style="traceback.border",
+ border_style="traceback.border.syntax_error",
expand=True,
padding=(0, 1),
width=self.width,