Загрузить файлы в «venv/Lib/site-packages/pip/_vendor/rich»
This commit is contained in:
54
venv/Lib/site-packages/pip/_vendor/rich/screen.py
Normal file
54
venv/Lib/site-packages/pip/_vendor/rich/screen.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from .segment import Segment
|
||||
from .style import StyleType
|
||||
from ._loop import loop_last
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import (
|
||||
Console,
|
||||
ConsoleOptions,
|
||||
RenderResult,
|
||||
RenderableType,
|
||||
Group,
|
||||
)
|
||||
|
||||
|
||||
class Screen:
|
||||
"""A renderable that fills the terminal screen and crops excess.
|
||||
|
||||
Args:
|
||||
renderable (RenderableType): Child renderable.
|
||||
style (StyleType, optional): Optional background style. Defaults to None.
|
||||
"""
|
||||
|
||||
renderable: "RenderableType"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*renderables: "RenderableType",
|
||||
style: Optional[StyleType] = None,
|
||||
application_mode: bool = False,
|
||||
) -> None:
|
||||
from pip._vendor.rich.console import Group
|
||||
|
||||
self.renderable = Group(*renderables)
|
||||
self.style = style
|
||||
self.application_mode = application_mode
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
width, height = options.size
|
||||
style = console.get_style(self.style) if self.style else None
|
||||
render_options = options.update(width=width, height=height)
|
||||
lines = console.render_lines(
|
||||
self.renderable or "", render_options, style=style, pad=True
|
||||
)
|
||||
lines = Segment.set_shape(lines, width, height, style=style)
|
||||
new_line = Segment("\n\r") if self.application_mode else Segment.line()
|
||||
for last, line in loop_last(lines):
|
||||
yield from line
|
||||
if not last:
|
||||
yield new_line
|
||||
752
venv/Lib/site-packages/pip/_vendor/rich/segment.py
Normal file
752
venv/Lib/site-packages/pip/_vendor/rich/segment.py
Normal file
@@ -0,0 +1,752 @@
|
||||
from enum import IntEnum
|
||||
from functools import lru_cache
|
||||
from itertools import filterfalse
|
||||
from logging import getLogger
|
||||
from operator import attrgetter
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
|
||||
from .cells import (
|
||||
_is_single_cell_widths,
|
||||
cached_cell_len,
|
||||
cell_len,
|
||||
get_character_cell_size,
|
||||
set_cell_size,
|
||||
)
|
||||
from .repr import Result, rich_repr
|
||||
from .style import Style
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
|
||||
log = getLogger("rich")
|
||||
|
||||
|
||||
class ControlType(IntEnum):
|
||||
"""Non-printable control codes which typically translate to ANSI codes."""
|
||||
|
||||
BELL = 1
|
||||
CARRIAGE_RETURN = 2
|
||||
HOME = 3
|
||||
CLEAR = 4
|
||||
SHOW_CURSOR = 5
|
||||
HIDE_CURSOR = 6
|
||||
ENABLE_ALT_SCREEN = 7
|
||||
DISABLE_ALT_SCREEN = 8
|
||||
CURSOR_UP = 9
|
||||
CURSOR_DOWN = 10
|
||||
CURSOR_FORWARD = 11
|
||||
CURSOR_BACKWARD = 12
|
||||
CURSOR_MOVE_TO_COLUMN = 13
|
||||
CURSOR_MOVE_TO = 14
|
||||
ERASE_IN_LINE = 15
|
||||
SET_WINDOW_TITLE = 16
|
||||
|
||||
|
||||
ControlCode = Union[
|
||||
Tuple[ControlType],
|
||||
Tuple[ControlType, Union[int, str]],
|
||||
Tuple[ControlType, int, int],
|
||||
]
|
||||
|
||||
|
||||
@rich_repr()
|
||||
class Segment(NamedTuple):
|
||||
"""A piece of text with associated style. Segments are produced by the Console render process and
|
||||
are ultimately converted in to strings to be written to the terminal.
|
||||
|
||||
Args:
|
||||
text (str): A piece of text.
|
||||
style (:class:`~rich.style.Style`, optional): An optional style to apply to the text.
|
||||
control (Tuple[ControlCode], optional): Optional sequence of control codes.
|
||||
|
||||
Attributes:
|
||||
cell_length (int): The cell length of this Segment.
|
||||
"""
|
||||
|
||||
text: str
|
||||
style: Optional[Style] = None
|
||||
control: Optional[Sequence[ControlCode]] = None
|
||||
|
||||
@property
|
||||
def cell_length(self) -> int:
|
||||
"""The number of terminal cells required to display self.text.
|
||||
|
||||
Returns:
|
||||
int: A number of cells.
|
||||
"""
|
||||
text, _style, control = self
|
||||
return 0 if control else cell_len(text)
|
||||
|
||||
def __rich_repr__(self) -> Result:
|
||||
yield self.text
|
||||
if self.control is None:
|
||||
if self.style is not None:
|
||||
yield self.style
|
||||
else:
|
||||
yield self.style
|
||||
yield self.control
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""Check if the segment contains text."""
|
||||
return bool(self.text)
|
||||
|
||||
@property
|
||||
def is_control(self) -> bool:
|
||||
"""Check if the segment contains control codes."""
|
||||
return self.control is not None
|
||||
|
||||
@classmethod
|
||||
@lru_cache(1024 * 16)
|
||||
def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]:
|
||||
"""Split a segment in to two at a given cell position.
|
||||
|
||||
Note that splitting a double-width character, may result in that character turning
|
||||
into two spaces.
|
||||
|
||||
Args:
|
||||
segment (Segment): A segment to split.
|
||||
cut (int): A cell position to cut on.
|
||||
|
||||
Returns:
|
||||
A tuple of two segments.
|
||||
"""
|
||||
text, style, control = segment
|
||||
_Segment = Segment
|
||||
cell_length = segment.cell_length
|
||||
if cut >= cell_length:
|
||||
return segment, _Segment("", style, control)
|
||||
|
||||
cell_size = get_character_cell_size
|
||||
|
||||
pos = int((cut / cell_length) * len(text))
|
||||
|
||||
while True:
|
||||
before = text[:pos]
|
||||
cell_pos = cell_len(before)
|
||||
out_by = cell_pos - cut
|
||||
if not out_by:
|
||||
return (
|
||||
_Segment(before, style, control),
|
||||
_Segment(text[pos:], style, control),
|
||||
)
|
||||
if out_by == -1 and cell_size(text[pos]) == 2:
|
||||
return (
|
||||
_Segment(text[:pos] + " ", style, control),
|
||||
_Segment(" " + text[pos + 1 :], style, control),
|
||||
)
|
||||
if out_by == +1 and cell_size(text[pos - 1]) == 2:
|
||||
return (
|
||||
_Segment(text[: pos - 1] + " ", style, control),
|
||||
_Segment(" " + text[pos:], style, control),
|
||||
)
|
||||
if cell_pos < cut:
|
||||
pos += 1
|
||||
else:
|
||||
pos -= 1
|
||||
|
||||
def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]:
|
||||
"""Split segment in to two segments at the specified column.
|
||||
|
||||
If the cut point falls in the middle of a 2-cell wide character then it is replaced
|
||||
by two spaces, to preserve the display width of the parent segment.
|
||||
|
||||
Args:
|
||||
cut (int): Offset within the segment to cut.
|
||||
|
||||
Returns:
|
||||
Tuple[Segment, Segment]: Two segments.
|
||||
"""
|
||||
text, style, control = self
|
||||
assert cut >= 0
|
||||
|
||||
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
|
||||
def line(cls) -> "Segment":
|
||||
"""Make a new line segment."""
|
||||
return cls("\n")
|
||||
|
||||
@classmethod
|
||||
def apply_style(
|
||||
cls,
|
||||
segments: Iterable["Segment"],
|
||||
style: Optional[Style] = None,
|
||||
post_style: Optional[Style] = None,
|
||||
) -> Iterable["Segment"]:
|
||||
"""Apply style(s) to an iterable of segments.
|
||||
|
||||
Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): Segments to process.
|
||||
style (Style, optional): Base style. Defaults to None.
|
||||
post_style (Style, optional): Style to apply on top of segment style. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Iterable[Segments]: A new iterable of segments (possibly the same iterable).
|
||||
"""
|
||||
result_segments = segments
|
||||
if style:
|
||||
apply = style.__add__
|
||||
result_segments = (
|
||||
cls(text, None if control else apply(_style), control)
|
||||
for text, _style, control in result_segments
|
||||
)
|
||||
if post_style:
|
||||
result_segments = (
|
||||
cls(
|
||||
text,
|
||||
(
|
||||
None
|
||||
if control
|
||||
else (_style + post_style if _style else post_style)
|
||||
),
|
||||
control,
|
||||
)
|
||||
for text, _style, control in result_segments
|
||||
)
|
||||
return result_segments
|
||||
|
||||
@classmethod
|
||||
def filter_control(
|
||||
cls, segments: Iterable["Segment"], is_control: bool = False
|
||||
) -> Iterable["Segment"]:
|
||||
"""Filter segments by ``is_control`` attribute.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable of Segment instances.
|
||||
is_control (bool, optional): is_control flag to match in search.
|
||||
|
||||
Returns:
|
||||
Iterable[Segment]: And iterable of Segment instances.
|
||||
|
||||
"""
|
||||
if is_control:
|
||||
return filter(attrgetter("control"), segments)
|
||||
else:
|
||||
return filterfalse(attrgetter("control"), segments)
|
||||
|
||||
@classmethod
|
||||
def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]:
|
||||
"""Split a sequence of segments in to a list of lines.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): Segments potentially containing line feeds.
|
||||
|
||||
Yields:
|
||||
Iterable[List[Segment]]: Iterable of segment lists, one per line.
|
||||
"""
|
||||
line: List[Segment] = []
|
||||
append = line.append
|
||||
|
||||
for segment in segments:
|
||||
if "\n" in segment.text and not segment.control:
|
||||
text, style, _ = segment
|
||||
while text:
|
||||
_text, new_line, text = text.partition("\n")
|
||||
if _text:
|
||||
append(cls(_text, style))
|
||||
if new_line:
|
||||
yield line
|
||||
line = []
|
||||
append = line.append
|
||||
else:
|
||||
append(segment)
|
||||
if line:
|
||||
yield line
|
||||
|
||||
@classmethod
|
||||
def split_and_crop_lines(
|
||||
cls,
|
||||
segments: Iterable["Segment"],
|
||||
length: int,
|
||||
style: Optional[Style] = None,
|
||||
pad: bool = True,
|
||||
include_new_lines: bool = True,
|
||||
) -> Iterable[List["Segment"]]:
|
||||
"""Split segments in to lines, and crop lines greater than a given length.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable of segments, probably
|
||||
generated from console.render.
|
||||
length (int): Desired line length.
|
||||
style (Style, optional): Style to use for any padding.
|
||||
pad (bool): Enable padding of lines that are less than `length`.
|
||||
|
||||
Returns:
|
||||
Iterable[List[Segment]]: An iterable of lines of segments.
|
||||
"""
|
||||
line: List[Segment] = []
|
||||
append = line.append
|
||||
|
||||
adjust_line_length = cls.adjust_line_length
|
||||
new_line_segment = cls("\n")
|
||||
|
||||
for segment in segments:
|
||||
if "\n" in segment.text and not segment.control:
|
||||
text, segment_style, _ = segment
|
||||
while text:
|
||||
_text, new_line, text = text.partition("\n")
|
||||
if _text:
|
||||
append(cls(_text, segment_style))
|
||||
if new_line:
|
||||
cropped_line = adjust_line_length(
|
||||
line, length, style=style, pad=pad
|
||||
)
|
||||
if include_new_lines:
|
||||
cropped_line.append(new_line_segment)
|
||||
yield cropped_line
|
||||
line.clear()
|
||||
else:
|
||||
append(segment)
|
||||
if line:
|
||||
yield adjust_line_length(line, length, style=style, pad=pad)
|
||||
|
||||
@classmethod
|
||||
def adjust_line_length(
|
||||
cls,
|
||||
line: List["Segment"],
|
||||
length: int,
|
||||
style: Optional[Style] = None,
|
||||
pad: bool = True,
|
||||
) -> List["Segment"]:
|
||||
"""Adjust a line to a given width (cropping or padding as required).
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): A list of segments in a single line.
|
||||
length (int): The desired width of the line.
|
||||
style (Style, optional): The style of padding if used (space on the end). Defaults to None.
|
||||
pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True.
|
||||
|
||||
Returns:
|
||||
List[Segment]: A line of segments with the desired length.
|
||||
"""
|
||||
line_length = sum(segment.cell_length for segment in line)
|
||||
new_line: List[Segment]
|
||||
|
||||
if line_length < length:
|
||||
if pad:
|
||||
new_line = line + [cls(" " * (length - line_length), style)]
|
||||
else:
|
||||
new_line = line[:]
|
||||
elif line_length > length:
|
||||
new_line = []
|
||||
append = new_line.append
|
||||
line_length = 0
|
||||
for segment in line:
|
||||
segment_length = segment.cell_length
|
||||
if line_length + segment_length < length or segment.control:
|
||||
append(segment)
|
||||
line_length += segment_length
|
||||
else:
|
||||
text, segment_style, _ = segment
|
||||
text = set_cell_size(text, length - line_length)
|
||||
append(cls(text, segment_style))
|
||||
break
|
||||
else:
|
||||
new_line = line[:]
|
||||
return new_line
|
||||
|
||||
@classmethod
|
||||
def get_line_length(cls, line: List["Segment"]) -> int:
|
||||
"""Get the length of list of segments.
|
||||
|
||||
Args:
|
||||
line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters),
|
||||
|
||||
Returns:
|
||||
int: The length of the line.
|
||||
"""
|
||||
_cell_len = cell_len
|
||||
return sum(_cell_len(text) for text, style, control in line if not control)
|
||||
|
||||
@classmethod
|
||||
def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]:
|
||||
"""Get the shape (enclosing rectangle) of a list of lines.
|
||||
|
||||
Args:
|
||||
lines (List[List[Segment]]): A list of lines (no '\\\\n' characters).
|
||||
|
||||
Returns:
|
||||
Tuple[int, int]: Width and height in characters.
|
||||
"""
|
||||
get_line_length = cls.get_line_length
|
||||
max_width = max(get_line_length(line) for line in lines) if lines else 0
|
||||
return (max_width, len(lines))
|
||||
|
||||
@classmethod
|
||||
def set_shape(
|
||||
cls,
|
||||
lines: List[List["Segment"]],
|
||||
width: int,
|
||||
height: Optional[int] = None,
|
||||
style: Optional[Style] = None,
|
||||
new_lines: bool = False,
|
||||
) -> List[List["Segment"]]:
|
||||
"""Set the shape of a list of lines (enclosing rectangle).
|
||||
|
||||
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, 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.
|
||||
"""
|
||||
_height = height or len(lines)
|
||||
|
||||
blank = (
|
||||
[cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)]
|
||||
)
|
||||
|
||||
adjust_line_length = cls.adjust_line_length
|
||||
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.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable of segments.
|
||||
|
||||
Returns:
|
||||
Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
|
||||
"""
|
||||
iter_segments = iter(segments)
|
||||
try:
|
||||
last_segment = next(iter_segments)
|
||||
except StopIteration:
|
||||
return
|
||||
|
||||
_Segment = Segment
|
||||
for segment in iter_segments:
|
||||
if last_segment.style == segment.style and not segment.control:
|
||||
last_segment = _Segment(
|
||||
last_segment.text + segment.text, last_segment.style
|
||||
)
|
||||
else:
|
||||
yield last_segment
|
||||
last_segment = segment
|
||||
yield last_segment
|
||||
|
||||
@classmethod
|
||||
def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
"""Remove all links from an iterable of styles.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable segments.
|
||||
|
||||
Yields:
|
||||
Segment: Segments with link removed.
|
||||
"""
|
||||
for segment in segments:
|
||||
if segment.control or segment.style is None:
|
||||
yield segment
|
||||
else:
|
||||
text, style, _control = segment
|
||||
yield cls(text, style.update_link(None) if style else None)
|
||||
|
||||
@classmethod
|
||||
def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
"""Remove all styles from an iterable of segments.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable segments.
|
||||
|
||||
Yields:
|
||||
Segment: Segments with styles replace with None
|
||||
"""
|
||||
for text, _style, control in segments:
|
||||
yield cls(text, None, control)
|
||||
|
||||
@classmethod
|
||||
def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
|
||||
"""Remove all color from an iterable of segments.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable segments.
|
||||
|
||||
Yields:
|
||||
Segment: Segments with colorless style.
|
||||
"""
|
||||
|
||||
cache: Dict[Style, Style] = {}
|
||||
for text, style, control in segments:
|
||||
if style:
|
||||
colorless_style = cache.get(style)
|
||||
if colorless_style is None:
|
||||
colorless_style = style.without_color
|
||||
cache[style] = colorless_style
|
||||
yield cls(text, colorless_style, control)
|
||||
else:
|
||||
yield cls(text, None, control)
|
||||
|
||||
@classmethod
|
||||
def divide(
|
||||
cls, segments: Iterable["Segment"], cuts: Iterable[int]
|
||||
) -> Iterable[List["Segment"]]:
|
||||
"""Divides an iterable of segments in to portions.
|
||||
|
||||
Args:
|
||||
cuts (Iterable[int]): Cell positions where to divide.
|
||||
|
||||
Yields:
|
||||
[Iterable[List[Segment]]]: An iterable of Segments in List.
|
||||
"""
|
||||
split_segments: List["Segment"] = []
|
||||
add_segment = split_segments.append
|
||||
|
||||
iter_cuts = iter(cuts)
|
||||
|
||||
while True:
|
||||
cut = next(iter_cuts, -1)
|
||||
if cut == -1:
|
||||
return
|
||||
if cut != 0:
|
||||
break
|
||||
yield []
|
||||
pos = 0
|
||||
|
||||
segments_clear = split_segments.clear
|
||||
segments_copy = split_segments.copy
|
||||
|
||||
_cell_len = cached_cell_len
|
||||
for segment in segments:
|
||||
text, _style, control = segment
|
||||
while text:
|
||||
end_pos = pos if control else pos + _cell_len(text)
|
||||
if end_pos < cut:
|
||||
add_segment(segment)
|
||||
pos = end_pos
|
||||
break
|
||||
|
||||
if end_pos == cut:
|
||||
add_segment(segment)
|
||||
yield segments_copy()
|
||||
segments_clear()
|
||||
pos = end_pos
|
||||
|
||||
cut = next(iter_cuts, -1)
|
||||
if cut == -1:
|
||||
if split_segments:
|
||||
yield segments_copy()
|
||||
return
|
||||
|
||||
break
|
||||
|
||||
else:
|
||||
before, segment = segment.split_cells(cut - pos)
|
||||
text, _style, control = segment
|
||||
add_segment(before)
|
||||
yield segments_copy()
|
||||
segments_clear()
|
||||
pos = cut
|
||||
|
||||
cut = next(iter_cuts, -1)
|
||||
if cut == -1:
|
||||
if split_segments:
|
||||
yield segments_copy()
|
||||
return
|
||||
|
||||
yield segments_copy()
|
||||
|
||||
|
||||
class Segments:
|
||||
"""A simple renderable to render an iterable of segments. This class may be useful if
|
||||
you want to print segments outside of a __rich_console__ method.
|
||||
|
||||
Args:
|
||||
segments (Iterable[Segment]): An iterable of segments.
|
||||
new_lines (bool, optional): Add new lines between segments. Defaults to False.
|
||||
"""
|
||||
|
||||
def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None:
|
||||
self.segments = list(segments)
|
||||
self.new_lines = new_lines
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
if self.new_lines:
|
||||
line = Segment.line()
|
||||
for segment in self.segments:
|
||||
yield segment
|
||||
yield line
|
||||
else:
|
||||
yield from self.segments
|
||||
|
||||
|
||||
class SegmentLines:
|
||||
def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None:
|
||||
"""A simple renderable containing a number of lines of segments. May be used as an intermediate
|
||||
in rendering process.
|
||||
|
||||
Args:
|
||||
lines (Iterable[List[Segment]]): Lists of segments forming lines.
|
||||
new_lines (bool, optional): Insert new lines after each line. Defaults to False.
|
||||
"""
|
||||
self.lines = list(lines)
|
||||
self.new_lines = new_lines
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
if self.new_lines:
|
||||
new_line = Segment.line()
|
||||
for line in self.lines:
|
||||
yield from line
|
||||
yield new_line
|
||||
else:
|
||||
for line in self.lines:
|
||||
yield from line
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich.console import Console
|
||||
from pip._vendor.rich.syntax import Syntax
|
||||
from pip._vendor.rich.text import Text
|
||||
|
||||
code = """from rich.console import Console
|
||||
console = Console()
|
||||
text = Text.from_markup("Hello, [bold magenta]World[/]!")
|
||||
console.print(text)"""
|
||||
|
||||
text = Text.from_markup("Hello, [bold magenta]World[/]!")
|
||||
|
||||
console = Console()
|
||||
|
||||
console.rule("rich.Segment")
|
||||
console.print(
|
||||
"A Segment is the last step in the Rich render process before generating text with ANSI codes."
|
||||
)
|
||||
console.print("\nConsider the following code:\n")
|
||||
console.print(Syntax(code, "python", line_numbers=True))
|
||||
console.print()
|
||||
console.print(
|
||||
"When you call [b]print()[/b], Rich [i]renders[/i] the object in to the following:\n"
|
||||
)
|
||||
fragments = list(console.render(text))
|
||||
console.print(fragments)
|
||||
console.print()
|
||||
console.print("The Segments are then processed to produce the following output:\n")
|
||||
console.print(text)
|
||||
console.print(
|
||||
"\nYou will only need to know this if you are implementing your own Rich renderables."
|
||||
)
|
||||
132
venv/Lib/site-packages/pip/_vendor/rich/spinner.py
Normal file
132
venv/Lib/site-packages/pip/_vendor/rich/spinner.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from typing import TYPE_CHECKING, List, Optional, Union, cast
|
||||
|
||||
from ._spinners import SPINNERS
|
||||
from .measure import Measurement
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from .style import StyleType
|
||||
|
||||
|
||||
class Spinner:
|
||||
"""A spinner animation.
|
||||
|
||||
Args:
|
||||
name (str): Name of spinner (run python -m rich.spinner).
|
||||
text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
|
||||
style (StyleType, optional): Style for spinner animation. Defaults to None.
|
||||
speed (float, optional): Speed factor for animation. Defaults to 1.0.
|
||||
|
||||
Raises:
|
||||
KeyError: If name isn't one of the supported spinner animations.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
text: "RenderableType" = "",
|
||||
*,
|
||||
style: Optional["StyleType"] = None,
|
||||
speed: float = 1.0,
|
||||
) -> None:
|
||||
try:
|
||||
spinner = SPINNERS[name]
|
||||
except KeyError:
|
||||
raise KeyError(f"no spinner called {name!r}")
|
||||
self.text: "Union[RenderableType, Text]" = (
|
||||
Text.from_markup(text) if isinstance(text, str) else text
|
||||
)
|
||||
self.name = name
|
||||
self.frames = cast(List[str], spinner["frames"])[:]
|
||||
self.interval = cast(float, spinner["interval"])
|
||||
self.start_time: Optional[float] = None
|
||||
self.style = style
|
||||
self.speed = speed
|
||||
self.frame_no_offset: float = 0.0
|
||||
self._update_speed = 0.0
|
||||
|
||||
def __rich_console__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> "RenderResult":
|
||||
yield self.render(console.get_time())
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: "Console", options: "ConsoleOptions"
|
||||
) -> Measurement:
|
||||
text = self.render(0)
|
||||
return Measurement.get(console, options, text)
|
||||
|
||||
def render(self, time: float) -> "RenderableType":
|
||||
"""Render the spinner for a given time.
|
||||
|
||||
Args:
|
||||
time (float): Time in seconds.
|
||||
|
||||
Returns:
|
||||
RenderableType: A renderable containing animation frame.
|
||||
"""
|
||||
if self.start_time is None:
|
||||
self.start_time = time
|
||||
|
||||
frame_no = ((time - self.start_time) * self.speed) / (
|
||||
self.interval / 1000.0
|
||||
) + self.frame_no_offset
|
||||
frame = Text(
|
||||
self.frames[int(frame_no) % len(self.frames)], style=self.style or ""
|
||||
)
|
||||
|
||||
if self._update_speed:
|
||||
self.frame_no_offset = frame_no
|
||||
self.start_time = time
|
||||
self.speed = self._update_speed
|
||||
self._update_speed = 0.0
|
||||
|
||||
if not self.text:
|
||||
return frame
|
||||
elif isinstance(self.text, (str, Text)):
|
||||
return Text.assemble(frame, " ", self.text)
|
||||
else:
|
||||
table = Table.grid(padding=1)
|
||||
table.add_row(frame, self.text)
|
||||
return table
|
||||
|
||||
def update(
|
||||
self,
|
||||
*,
|
||||
text: "RenderableType" = "",
|
||||
style: Optional["StyleType"] = None,
|
||||
speed: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Updates attributes of a spinner after it has been started.
|
||||
|
||||
Args:
|
||||
text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
|
||||
style (StyleType, optional): Style for spinner animation. Defaults to None.
|
||||
speed (float, optional): Speed factor for animation. Defaults to None.
|
||||
"""
|
||||
if text:
|
||||
self.text = Text.from_markup(text) if isinstance(text, str) else text
|
||||
if style:
|
||||
self.style = style
|
||||
if speed:
|
||||
self._update_speed = speed
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from time import sleep
|
||||
|
||||
from .console import Group
|
||||
from .live import Live
|
||||
|
||||
all_spinners = Group(
|
||||
*[
|
||||
Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
|
||||
for spinner_name in sorted(SPINNERS.keys())
|
||||
]
|
||||
)
|
||||
|
||||
with Live(all_spinners, refresh_per_second=20) as live:
|
||||
while True:
|
||||
sleep(0.1)
|
||||
131
venv/Lib/site-packages/pip/_vendor/rich/status.py
Normal file
131
venv/Lib/site-packages/pip/_vendor/rich/status.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from types import TracebackType
|
||||
from typing import Optional, Type
|
||||
|
||||
from .console import Console, RenderableType
|
||||
from .jupyter import JupyterMixin
|
||||
from .live import Live
|
||||
from .spinner import Spinner
|
||||
from .style import StyleType
|
||||
|
||||
|
||||
class Status(JupyterMixin):
|
||||
"""Displays a status indicator with a 'spinner' animation.
|
||||
|
||||
Args:
|
||||
status (RenderableType): A status renderable (str or Text typically).
|
||||
console (Console, optional): Console instance to use, or None for global console. Defaults to None.
|
||||
spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
|
||||
spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
|
||||
speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
|
||||
refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status: RenderableType,
|
||||
*,
|
||||
console: Optional[Console] = None,
|
||||
spinner: str = "dots",
|
||||
spinner_style: StyleType = "status.spinner",
|
||||
speed: float = 1.0,
|
||||
refresh_per_second: float = 12.5,
|
||||
):
|
||||
self.status = status
|
||||
self.spinner_style = spinner_style
|
||||
self.speed = speed
|
||||
self._spinner = Spinner(spinner, text=status, style=spinner_style, speed=speed)
|
||||
self._live = Live(
|
||||
self.renderable,
|
||||
console=console,
|
||||
refresh_per_second=refresh_per_second,
|
||||
transient=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def renderable(self) -> Spinner:
|
||||
return self._spinner
|
||||
|
||||
@property
|
||||
def console(self) -> "Console":
|
||||
"""Get the Console used by the Status objects."""
|
||||
return self._live.console
|
||||
|
||||
def update(
|
||||
self,
|
||||
status: Optional[RenderableType] = None,
|
||||
*,
|
||||
spinner: Optional[str] = None,
|
||||
spinner_style: Optional[StyleType] = None,
|
||||
speed: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Update status.
|
||||
|
||||
Args:
|
||||
status (Optional[RenderableType], optional): New status renderable or None for no change. Defaults to None.
|
||||
spinner (Optional[str], optional): New spinner or None for no change. Defaults to None.
|
||||
spinner_style (Optional[StyleType], optional): New spinner style or None for no change. Defaults to None.
|
||||
speed (Optional[float], optional): Speed factor for spinner animation or None for no change. Defaults to None.
|
||||
"""
|
||||
if status is not None:
|
||||
self.status = status
|
||||
if spinner_style is not None:
|
||||
self.spinner_style = spinner_style
|
||||
if speed is not None:
|
||||
self.speed = speed
|
||||
if spinner is not None:
|
||||
self._spinner = Spinner(
|
||||
spinner, text=self.status, style=self.spinner_style, speed=self.speed
|
||||
)
|
||||
self._live.update(self.renderable, refresh=True)
|
||||
else:
|
||||
self._spinner.update(
|
||||
text=self.status, style=self.spinner_style, speed=self.speed
|
||||
)
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the status animation."""
|
||||
self._live.start()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Stop the spinner animation."""
|
||||
self._live.stop()
|
||||
|
||||
def __rich__(self) -> RenderableType:
|
||||
return self.renderable
|
||||
|
||||
def __enter__(self) -> "Status":
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
self.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from time import sleep
|
||||
|
||||
from .console import Console
|
||||
|
||||
console = Console()
|
||||
with console.status("[magenta]Covid detector booting up") as status:
|
||||
sleep(3)
|
||||
console.log("Importing advanced AI")
|
||||
sleep(3)
|
||||
console.log("Advanced Covid AI Ready")
|
||||
sleep(3)
|
||||
status.update(status="[bold blue] Scanning for Covid", spinner="earth")
|
||||
sleep(3)
|
||||
console.log("Found 10,000,000,000 copies of Covid32.exe")
|
||||
sleep(3)
|
||||
status.update(
|
||||
status="[bold red]Moving Covid32.exe to Trash",
|
||||
spinner="bouncingBall",
|
||||
spinner_style="yellow",
|
||||
)
|
||||
sleep(5)
|
||||
console.print("[bold green]Covid deleted successfully")
|
||||
792
venv/Lib/site-packages/pip/_vendor/rich/style.py
Normal file
792
venv/Lib/site-packages/pip/_vendor/rich/style.py
Normal file
@@ -0,0 +1,792 @@
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from operator import attrgetter
|
||||
from pickle import dumps, loads
|
||||
from random import randint
|
||||
from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast
|
||||
|
||||
from . import errors
|
||||
from .color import Color, ColorParseError, ColorSystem, blend_rgb
|
||||
from .repr import Result, rich_repr
|
||||
from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
|
||||
|
||||
_hash_getter = attrgetter(
|
||||
"_color", "_bgcolor", "_attributes", "_set_attributes", "_link", "_meta"
|
||||
)
|
||||
|
||||
# Style instances and style definitions are often interchangeable
|
||||
StyleType = Union[str, "Style"]
|
||||
|
||||
|
||||
class _Bit:
|
||||
"""A descriptor to get/set a style attribute bit."""
|
||||
|
||||
__slots__ = ["bit"]
|
||||
|
||||
def __init__(self, bit_no: int) -> None:
|
||||
self.bit = 1 << bit_no
|
||||
|
||||
def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]:
|
||||
if obj._set_attributes & self.bit:
|
||||
return obj._attributes & self.bit != 0
|
||||
return None
|
||||
|
||||
|
||||
@rich_repr
|
||||
class Style:
|
||||
"""A terminal style.
|
||||
|
||||
A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such
|
||||
as bold, italic etc. The attributes have 3 states: they can either be on
|
||||
(``True``), off (``False``), or not set (``None``).
|
||||
|
||||
Args:
|
||||
color (Union[Color, str], optional): Color of terminal text. Defaults to None.
|
||||
bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None.
|
||||
bold (bool, optional): Enable bold text. Defaults to None.
|
||||
dim (bool, optional): Enable dim text. Defaults to None.
|
||||
italic (bool, optional): Enable italic text. Defaults to None.
|
||||
underline (bool, optional): Enable underlined text. Defaults to None.
|
||||
blink (bool, optional): Enabled blinking text. Defaults to None.
|
||||
blink2 (bool, optional): Enable fast blinking text. Defaults to None.
|
||||
reverse (bool, optional): Enabled reverse text. Defaults to None.
|
||||
conceal (bool, optional): Enable concealed text. Defaults to None.
|
||||
strike (bool, optional): Enable strikethrough text. Defaults to None.
|
||||
underline2 (bool, optional): Enable doubly underlined text. Defaults to None.
|
||||
frame (bool, optional): Enable framed text. Defaults to None.
|
||||
encircle (bool, optional): Enable encircled text. Defaults to None.
|
||||
overline (bool, optional): Enable overlined text. Defaults to None.
|
||||
link (str, link): Link URL. Defaults to None.
|
||||
|
||||
"""
|
||||
|
||||
_color: Optional[Color]
|
||||
_bgcolor: Optional[Color]
|
||||
_attributes: int
|
||||
_set_attributes: int
|
||||
_hash: Optional[int]
|
||||
_null: bool
|
||||
_meta: Optional[bytes]
|
||||
|
||||
__slots__ = [
|
||||
"_color",
|
||||
"_bgcolor",
|
||||
"_attributes",
|
||||
"_set_attributes",
|
||||
"_link",
|
||||
"_link_id",
|
||||
"_ansi",
|
||||
"_style_definition",
|
||||
"_hash",
|
||||
"_null",
|
||||
"_meta",
|
||||
]
|
||||
|
||||
# maps bits on to SGR parameter
|
||||
_style_map = {
|
||||
0: "1",
|
||||
1: "2",
|
||||
2: "3",
|
||||
3: "4",
|
||||
4: "5",
|
||||
5: "6",
|
||||
6: "7",
|
||||
7: "8",
|
||||
8: "9",
|
||||
9: "21",
|
||||
10: "51",
|
||||
11: "52",
|
||||
12: "53",
|
||||
}
|
||||
|
||||
STYLE_ATTRIBUTES = {
|
||||
"dim": "dim",
|
||||
"d": "dim",
|
||||
"bold": "bold",
|
||||
"b": "bold",
|
||||
"italic": "italic",
|
||||
"i": "italic",
|
||||
"underline": "underline",
|
||||
"u": "underline",
|
||||
"blink": "blink",
|
||||
"blink2": "blink2",
|
||||
"reverse": "reverse",
|
||||
"r": "reverse",
|
||||
"conceal": "conceal",
|
||||
"c": "conceal",
|
||||
"strike": "strike",
|
||||
"s": "strike",
|
||||
"underline2": "underline2",
|
||||
"uu": "underline2",
|
||||
"frame": "frame",
|
||||
"encircle": "encircle",
|
||||
"overline": "overline",
|
||||
"o": "overline",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
color: Optional[Union[Color, str]] = None,
|
||||
bgcolor: Optional[Union[Color, str]] = None,
|
||||
bold: Optional[bool] = None,
|
||||
dim: Optional[bool] = None,
|
||||
italic: Optional[bool] = None,
|
||||
underline: Optional[bool] = None,
|
||||
blink: Optional[bool] = None,
|
||||
blink2: Optional[bool] = None,
|
||||
reverse: Optional[bool] = None,
|
||||
conceal: Optional[bool] = None,
|
||||
strike: Optional[bool] = None,
|
||||
underline2: Optional[bool] = None,
|
||||
frame: Optional[bool] = None,
|
||||
encircle: Optional[bool] = None,
|
||||
overline: Optional[bool] = None,
|
||||
link: Optional[str] = None,
|
||||
meta: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
self._ansi: Optional[str] = None
|
||||
self._style_definition: Optional[str] = None
|
||||
|
||||
def _make_color(color: Union[Color, str]) -> Color:
|
||||
return color if isinstance(color, Color) else Color.parse(color)
|
||||
|
||||
self._color = None if color is None else _make_color(color)
|
||||
self._bgcolor = None if bgcolor is None else _make_color(bgcolor)
|
||||
self._set_attributes = sum(
|
||||
(
|
||||
bold is not None,
|
||||
dim is not None and 2,
|
||||
italic is not None and 4,
|
||||
underline is not None and 8,
|
||||
blink is not None and 16,
|
||||
blink2 is not None and 32,
|
||||
reverse is not None and 64,
|
||||
conceal is not None and 128,
|
||||
strike is not None and 256,
|
||||
underline2 is not None and 512,
|
||||
frame is not None and 1024,
|
||||
encircle is not None and 2048,
|
||||
overline is not None and 4096,
|
||||
)
|
||||
)
|
||||
self._attributes = (
|
||||
sum(
|
||||
(
|
||||
bold and 1 or 0,
|
||||
dim and 2 or 0,
|
||||
italic and 4 or 0,
|
||||
underline and 8 or 0,
|
||||
blink and 16 or 0,
|
||||
blink2 and 32 or 0,
|
||||
reverse and 64 or 0,
|
||||
conceal and 128 or 0,
|
||||
strike and 256 or 0,
|
||||
underline2 and 512 or 0,
|
||||
frame and 1024 or 0,
|
||||
encircle and 2048 or 0,
|
||||
overline and 4096 or 0,
|
||||
)
|
||||
)
|
||||
if self._set_attributes
|
||||
else 0
|
||||
)
|
||||
|
||||
self._link = link
|
||||
self._meta = None if meta is None else dumps(meta)
|
||||
self._link_id = (
|
||||
f"{randint(0, 999999)}{hash(self._meta)}" if (link or meta) else ""
|
||||
)
|
||||
self._hash: Optional[int] = None
|
||||
self._null = not (self._set_attributes or color or bgcolor or link or meta)
|
||||
|
||||
@classmethod
|
||||
def null(cls) -> "Style":
|
||||
"""Create an 'null' style, equivalent to Style(), but more performant."""
|
||||
return NULL_STYLE
|
||||
|
||||
@classmethod
|
||||
def from_color(
|
||||
cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None
|
||||
) -> "Style":
|
||||
"""Create a new style with colors and no attributes.
|
||||
|
||||
Returns:
|
||||
color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None.
|
||||
bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None.
|
||||
"""
|
||||
style: Style = cls.__new__(Style)
|
||||
style._ansi = None
|
||||
style._style_definition = None
|
||||
style._color = color
|
||||
style._bgcolor = bgcolor
|
||||
style._set_attributes = 0
|
||||
style._attributes = 0
|
||||
style._link = None
|
||||
style._link_id = ""
|
||||
style._meta = None
|
||||
style._null = not (color or bgcolor)
|
||||
style._hash = None
|
||||
return style
|
||||
|
||||
@classmethod
|
||||
def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style":
|
||||
"""Create a new style with meta data.
|
||||
|
||||
Returns:
|
||||
meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None.
|
||||
"""
|
||||
style: Style = cls.__new__(Style)
|
||||
style._ansi = None
|
||||
style._style_definition = None
|
||||
style._color = None
|
||||
style._bgcolor = None
|
||||
style._set_attributes = 0
|
||||
style._attributes = 0
|
||||
style._link = None
|
||||
style._meta = dumps(meta)
|
||||
style._link_id = f"{randint(0, 999999)}{hash(style._meta)}"
|
||||
style._hash = None
|
||||
style._null = not (meta)
|
||||
return style
|
||||
|
||||
@classmethod
|
||||
def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style":
|
||||
"""Create a blank style with meta information.
|
||||
|
||||
Example:
|
||||
style = Style.on(click=self.on_click)
|
||||
|
||||
Args:
|
||||
meta (Optional[Dict[str, Any]], optional): An optional dict of meta information.
|
||||
**handlers (Any): Keyword arguments are translated in to handlers.
|
||||
|
||||
Returns:
|
||||
Style: A Style with meta information attached.
|
||||
"""
|
||||
meta = {} if meta is None else meta
|
||||
meta.update({f"@{key}": value for key, value in handlers.items()})
|
||||
return cls.from_meta(meta)
|
||||
|
||||
bold = _Bit(0)
|
||||
dim = _Bit(1)
|
||||
italic = _Bit(2)
|
||||
underline = _Bit(3)
|
||||
blink = _Bit(4)
|
||||
blink2 = _Bit(5)
|
||||
reverse = _Bit(6)
|
||||
conceal = _Bit(7)
|
||||
strike = _Bit(8)
|
||||
underline2 = _Bit(9)
|
||||
frame = _Bit(10)
|
||||
encircle = _Bit(11)
|
||||
overline = _Bit(12)
|
||||
|
||||
@property
|
||||
def link_id(self) -> str:
|
||||
"""Get a link id, used in ansi code for links."""
|
||||
return self._link_id
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Re-generate style definition from attributes."""
|
||||
if self._style_definition is None:
|
||||
attributes: List[str] = []
|
||||
append = attributes.append
|
||||
bits = self._set_attributes
|
||||
if bits & 0b0000000001111:
|
||||
if bits & 1:
|
||||
append("bold" if self.bold else "not bold")
|
||||
if bits & (1 << 1):
|
||||
append("dim" if self.dim else "not dim")
|
||||
if bits & (1 << 2):
|
||||
append("italic" if self.italic else "not italic")
|
||||
if bits & (1 << 3):
|
||||
append("underline" if self.underline else "not underline")
|
||||
if bits & 0b0000111110000:
|
||||
if bits & (1 << 4):
|
||||
append("blink" if self.blink else "not blink")
|
||||
if bits & (1 << 5):
|
||||
append("blink2" if self.blink2 else "not blink2")
|
||||
if bits & (1 << 6):
|
||||
append("reverse" if self.reverse else "not reverse")
|
||||
if bits & (1 << 7):
|
||||
append("conceal" if self.conceal else "not conceal")
|
||||
if bits & (1 << 8):
|
||||
append("strike" if self.strike else "not strike")
|
||||
if bits & 0b1111000000000:
|
||||
if bits & (1 << 9):
|
||||
append("underline2" if self.underline2 else "not underline2")
|
||||
if bits & (1 << 10):
|
||||
append("frame" if self.frame else "not frame")
|
||||
if bits & (1 << 11):
|
||||
append("encircle" if self.encircle else "not encircle")
|
||||
if bits & (1 << 12):
|
||||
append("overline" if self.overline else "not overline")
|
||||
if self._color is not None:
|
||||
append(self._color.name)
|
||||
if self._bgcolor is not None:
|
||||
append("on")
|
||||
append(self._bgcolor.name)
|
||||
if self._link:
|
||||
append("link")
|
||||
append(self._link)
|
||||
self._style_definition = " ".join(attributes) or "none"
|
||||
return self._style_definition
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""A Style is false if it has no attributes, colors, or links."""
|
||||
return not self._null
|
||||
|
||||
def _make_ansi_codes(self, color_system: ColorSystem) -> str:
|
||||
"""Generate ANSI codes for this style.
|
||||
|
||||
Args:
|
||||
color_system (ColorSystem): Color system.
|
||||
|
||||
Returns:
|
||||
str: String containing codes.
|
||||
"""
|
||||
|
||||
if self._ansi is None:
|
||||
sgr: List[str] = []
|
||||
append = sgr.append
|
||||
_style_map = self._style_map
|
||||
attributes = self._attributes & self._set_attributes
|
||||
if attributes:
|
||||
if attributes & 1:
|
||||
append(_style_map[0])
|
||||
if attributes & 2:
|
||||
append(_style_map[1])
|
||||
if attributes & 4:
|
||||
append(_style_map[2])
|
||||
if attributes & 8:
|
||||
append(_style_map[3])
|
||||
if attributes & 0b0000111110000:
|
||||
for bit in range(4, 9):
|
||||
if attributes & (1 << bit):
|
||||
append(_style_map[bit])
|
||||
if attributes & 0b1111000000000:
|
||||
for bit in range(9, 13):
|
||||
if attributes & (1 << bit):
|
||||
append(_style_map[bit])
|
||||
if self._color is not None:
|
||||
sgr.extend(self._color.downgrade(color_system).get_ansi_codes())
|
||||
if self._bgcolor is not None:
|
||||
sgr.extend(
|
||||
self._bgcolor.downgrade(color_system).get_ansi_codes(
|
||||
foreground=False
|
||||
)
|
||||
)
|
||||
self._ansi = ";".join(sgr)
|
||||
return self._ansi
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=1024)
|
||||
def normalize(cls, style: str) -> str:
|
||||
"""Normalize a style definition so that styles with the same effect have the same string
|
||||
representation.
|
||||
|
||||
Args:
|
||||
style (str): A style definition.
|
||||
|
||||
Returns:
|
||||
str: Normal form of style definition.
|
||||
"""
|
||||
try:
|
||||
return str(cls.parse(style))
|
||||
except errors.StyleSyntaxError:
|
||||
return style.strip().lower()
|
||||
|
||||
@classmethod
|
||||
def pick_first(cls, *values: Optional[StyleType]) -> StyleType:
|
||||
"""Pick first non-None style."""
|
||||
for value in values:
|
||||
if value is not None:
|
||||
return value
|
||||
raise ValueError("expected at least one non-None style")
|
||||
|
||||
def __rich_repr__(self) -> Result:
|
||||
yield "color", self.color, None
|
||||
yield "bgcolor", self.bgcolor, None
|
||||
yield "bold", self.bold, None,
|
||||
yield "dim", self.dim, None,
|
||||
yield "italic", self.italic, None
|
||||
yield "underline", self.underline, None,
|
||||
yield "blink", self.blink, None
|
||||
yield "blink2", self.blink2, None
|
||||
yield "reverse", self.reverse, None
|
||||
yield "conceal", self.conceal, None
|
||||
yield "strike", self.strike, None
|
||||
yield "underline2", self.underline2, None
|
||||
yield "frame", self.frame, None
|
||||
yield "encircle", self.encircle, None
|
||||
yield "link", self.link, None
|
||||
if self._meta:
|
||||
yield "meta", self.meta
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Style):
|
||||
return NotImplemented
|
||||
return self.__hash__() == other.__hash__()
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Style):
|
||||
return NotImplemented
|
||||
return self.__hash__() != other.__hash__()
|
||||
|
||||
def __hash__(self) -> int:
|
||||
if self._hash is not None:
|
||||
return self._hash
|
||||
self._hash = hash(_hash_getter(self))
|
||||
return self._hash
|
||||
|
||||
@property
|
||||
def color(self) -> Optional[Color]:
|
||||
"""The foreground color or None if it is not set."""
|
||||
return self._color
|
||||
|
||||
@property
|
||||
def bgcolor(self) -> Optional[Color]:
|
||||
"""The background color or None if it is not set."""
|
||||
return self._bgcolor
|
||||
|
||||
@property
|
||||
def link(self) -> Optional[str]:
|
||||
"""Link text, if set."""
|
||||
return self._link
|
||||
|
||||
@property
|
||||
def transparent_background(self) -> bool:
|
||||
"""Check if the style specified a transparent background."""
|
||||
return self.bgcolor is None or self.bgcolor.is_default
|
||||
|
||||
@property
|
||||
def background_style(self) -> "Style":
|
||||
"""A Style with background only."""
|
||||
return Style(bgcolor=self.bgcolor)
|
||||
|
||||
@property
|
||||
def meta(self) -> Dict[str, Any]:
|
||||
"""Get meta information (can not be changed after construction)."""
|
||||
return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta))
|
||||
|
||||
@property
|
||||
def without_color(self) -> "Style":
|
||||
"""Get a copy of the style with color removed."""
|
||||
if self._null:
|
||||
return NULL_STYLE
|
||||
style: Style = self.__new__(Style)
|
||||
style._ansi = None
|
||||
style._style_definition = None
|
||||
style._color = None
|
||||
style._bgcolor = None
|
||||
style._attributes = self._attributes
|
||||
style._set_attributes = self._set_attributes
|
||||
style._link = self._link
|
||||
style._link_id = f"{randint(0, 999999)}" if self._link else ""
|
||||
style._null = False
|
||||
style._meta = None
|
||||
style._hash = None
|
||||
return style
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=4096)
|
||||
def parse(cls, style_definition: str) -> "Style":
|
||||
"""Parse a style definition.
|
||||
|
||||
Args:
|
||||
style_definition (str): A string containing a style.
|
||||
|
||||
Raises:
|
||||
errors.StyleSyntaxError: If the style definition syntax is invalid.
|
||||
|
||||
Returns:
|
||||
`Style`: A Style instance.
|
||||
"""
|
||||
if style_definition.strip() == "none" or not style_definition:
|
||||
return cls.null()
|
||||
|
||||
STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES
|
||||
color: Optional[str] = None
|
||||
bgcolor: Optional[str] = None
|
||||
attributes: Dict[str, Optional[Any]] = {}
|
||||
link: Optional[str] = None
|
||||
|
||||
words = iter(style_definition.split())
|
||||
for original_word in words:
|
||||
word = original_word.lower()
|
||||
if word == "on":
|
||||
word = next(words, "")
|
||||
if not word:
|
||||
raise errors.StyleSyntaxError("color expected after 'on'")
|
||||
try:
|
||||
Color.parse(word)
|
||||
except ColorParseError as error:
|
||||
raise errors.StyleSyntaxError(
|
||||
f"unable to parse {word!r} as background color; {error}"
|
||||
) from None
|
||||
bgcolor = word
|
||||
|
||||
elif word == "not":
|
||||
word = next(words, "")
|
||||
attribute = STYLE_ATTRIBUTES.get(word)
|
||||
if attribute is None:
|
||||
raise errors.StyleSyntaxError(
|
||||
f"expected style attribute after 'not', found {word!r}"
|
||||
)
|
||||
attributes[attribute] = False
|
||||
|
||||
elif word == "link":
|
||||
word = next(words, "")
|
||||
if not word:
|
||||
raise errors.StyleSyntaxError("URL expected after 'link'")
|
||||
link = word
|
||||
|
||||
elif word in STYLE_ATTRIBUTES:
|
||||
attributes[STYLE_ATTRIBUTES[word]] = True
|
||||
|
||||
else:
|
||||
try:
|
||||
Color.parse(word)
|
||||
except ColorParseError as error:
|
||||
raise errors.StyleSyntaxError(
|
||||
f"unable to parse {word!r} as color; {error}"
|
||||
) from None
|
||||
color = word
|
||||
style = Style(color=color, bgcolor=bgcolor, link=link, **attributes)
|
||||
return style
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def get_html_style(self, theme: Optional[TerminalTheme] = None) -> str:
|
||||
"""Get a CSS style rule."""
|
||||
theme = theme or DEFAULT_TERMINAL_THEME
|
||||
css: List[str] = []
|
||||
append = css.append
|
||||
|
||||
color = self.color
|
||||
bgcolor = self.bgcolor
|
||||
if self.reverse:
|
||||
color, bgcolor = bgcolor, color
|
||||
if self.dim:
|
||||
foreground_color = (
|
||||
theme.foreground_color if color is None else color.get_truecolor(theme)
|
||||
)
|
||||
color = Color.from_triplet(
|
||||
blend_rgb(foreground_color, theme.background_color, 0.5)
|
||||
)
|
||||
if color is not None:
|
||||
theme_color = color.get_truecolor(theme)
|
||||
append(f"color: {theme_color.hex}")
|
||||
append(f"text-decoration-color: {theme_color.hex}")
|
||||
if bgcolor is not None:
|
||||
theme_color = bgcolor.get_truecolor(theme, foreground=False)
|
||||
append(f"background-color: {theme_color.hex}")
|
||||
if self.bold:
|
||||
append("font-weight: bold")
|
||||
if self.italic:
|
||||
append("font-style: italic")
|
||||
if self.underline:
|
||||
append("text-decoration: underline")
|
||||
if self.strike:
|
||||
append("text-decoration: line-through")
|
||||
if self.overline:
|
||||
append("text-decoration: overline")
|
||||
return "; ".join(css)
|
||||
|
||||
@classmethod
|
||||
def combine(cls, styles: Iterable["Style"]) -> "Style":
|
||||
"""Combine styles and get result.
|
||||
|
||||
Args:
|
||||
styles (Iterable[Style]): Styles to combine.
|
||||
|
||||
Returns:
|
||||
Style: A new style instance.
|
||||
"""
|
||||
iter_styles = iter(styles)
|
||||
return sum(iter_styles, next(iter_styles))
|
||||
|
||||
@classmethod
|
||||
def chain(cls, *styles: "Style") -> "Style":
|
||||
"""Combine styles from positional argument in to a single style.
|
||||
|
||||
Args:
|
||||
*styles (Iterable[Style]): Styles to combine.
|
||||
|
||||
Returns:
|
||||
Style: A new style instance.
|
||||
"""
|
||||
iter_styles = iter(styles)
|
||||
return sum(iter_styles, next(iter_styles))
|
||||
|
||||
def copy(self) -> "Style":
|
||||
"""Get a copy of this style.
|
||||
|
||||
Returns:
|
||||
Style: A new Style instance with identical attributes.
|
||||
"""
|
||||
if self._null:
|
||||
return NULL_STYLE
|
||||
style: Style = self.__new__(Style)
|
||||
style._ansi = self._ansi
|
||||
style._style_definition = self._style_definition
|
||||
style._color = self._color
|
||||
style._bgcolor = self._bgcolor
|
||||
style._attributes = self._attributes
|
||||
style._set_attributes = self._set_attributes
|
||||
style._link = self._link
|
||||
style._link_id = f"{randint(0, 999999)}" if self._link else ""
|
||||
style._hash = self._hash
|
||||
style._null = False
|
||||
style._meta = self._meta
|
||||
return style
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def clear_meta_and_links(self) -> "Style":
|
||||
"""Get a copy of this style with link and meta information removed.
|
||||
|
||||
Returns:
|
||||
Style: New style object.
|
||||
"""
|
||||
if self._null:
|
||||
return NULL_STYLE
|
||||
style: Style = self.__new__(Style)
|
||||
style._ansi = self._ansi
|
||||
style._style_definition = self._style_definition
|
||||
style._color = self._color
|
||||
style._bgcolor = self._bgcolor
|
||||
style._attributes = self._attributes
|
||||
style._set_attributes = self._set_attributes
|
||||
style._link = None
|
||||
style._link_id = ""
|
||||
style._hash = None
|
||||
style._null = False
|
||||
style._meta = None
|
||||
return style
|
||||
|
||||
def update_link(self, link: Optional[str] = None) -> "Style":
|
||||
"""Get a copy with a different value for link.
|
||||
|
||||
Args:
|
||||
link (str, optional): New value for link. Defaults to None.
|
||||
|
||||
Returns:
|
||||
Style: A new Style instance.
|
||||
"""
|
||||
style: Style = self.__new__(Style)
|
||||
style._ansi = self._ansi
|
||||
style._style_definition = self._style_definition
|
||||
style._color = self._color
|
||||
style._bgcolor = self._bgcolor
|
||||
style._attributes = self._attributes
|
||||
style._set_attributes = self._set_attributes
|
||||
style._link = link
|
||||
style._link_id = f"{randint(0, 999999)}" if link else ""
|
||||
style._hash = None
|
||||
style._null = False
|
||||
style._meta = self._meta
|
||||
return style
|
||||
|
||||
def render(
|
||||
self,
|
||||
text: str = "",
|
||||
*,
|
||||
color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
|
||||
legacy_windows: bool = False,
|
||||
) -> str:
|
||||
"""Render the ANSI codes for the style.
|
||||
|
||||
Args:
|
||||
text (str, optional): A string to style. Defaults to "".
|
||||
color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.
|
||||
|
||||
Returns:
|
||||
str: A string containing ANSI style codes.
|
||||
"""
|
||||
if not text or color_system is None:
|
||||
return text
|
||||
attrs = self._ansi or self._make_ansi_codes(color_system)
|
||||
rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
|
||||
if self._link and not legacy_windows:
|
||||
rendered = (
|
||||
f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
|
||||
)
|
||||
return rendered
|
||||
|
||||
def test(self, text: Optional[str] = None) -> None:
|
||||
"""Write text with style directly to terminal.
|
||||
|
||||
This method is for testing purposes only.
|
||||
|
||||
Args:
|
||||
text (Optional[str], optional): Text to style or None for style name.
|
||||
|
||||
"""
|
||||
text = text or str(self)
|
||||
sys.stdout.write(f"{self.render(text)}\n")
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def _add(self, style: Optional["Style"]) -> "Style":
|
||||
if style is None or style._null:
|
||||
return self
|
||||
if self._null:
|
||||
return style
|
||||
new_style: Style = self.__new__(Style)
|
||||
new_style._ansi = None
|
||||
new_style._style_definition = None
|
||||
new_style._color = style._color or self._color
|
||||
new_style._bgcolor = style._bgcolor or self._bgcolor
|
||||
new_style._attributes = (self._attributes & ~style._set_attributes) | (
|
||||
style._attributes & style._set_attributes
|
||||
)
|
||||
new_style._set_attributes = self._set_attributes | style._set_attributes
|
||||
new_style._link = style._link or self._link
|
||||
new_style._link_id = style._link_id or self._link_id
|
||||
new_style._null = style._null
|
||||
if self._meta and style._meta:
|
||||
new_style._meta = dumps({**self.meta, **style.meta})
|
||||
else:
|
||||
new_style._meta = self._meta or style._meta
|
||||
new_style._hash = None
|
||||
return new_style
|
||||
|
||||
def __add__(self, style: Optional["Style"]) -> "Style":
|
||||
combined_style = self._add(style)
|
||||
return combined_style.copy() if combined_style.link else combined_style
|
||||
|
||||
|
||||
NULL_STYLE = Style()
|
||||
|
||||
|
||||
class StyleStack:
|
||||
"""A stack of styles."""
|
||||
|
||||
__slots__ = ["_stack"]
|
||||
|
||||
def __init__(self, default_style: "Style") -> None:
|
||||
self._stack: List[Style] = [default_style]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<stylestack {self._stack!r}>"
|
||||
|
||||
@property
|
||||
def current(self) -> Style:
|
||||
"""Get the Style at the top of the stack."""
|
||||
return self._stack[-1]
|
||||
|
||||
def push(self, style: Style) -> None:
|
||||
"""Push a new style on to the stack.
|
||||
|
||||
Args:
|
||||
style (Style): New style to combine with current style.
|
||||
"""
|
||||
self._stack.append(self._stack[-1] + style)
|
||||
|
||||
def pop(self) -> Style:
|
||||
"""Pop last style and discard.
|
||||
|
||||
Returns:
|
||||
Style: New current style (also available as stack.current)
|
||||
"""
|
||||
self._stack.pop()
|
||||
return self._stack[-1]
|
||||
Reference in New Issue
Block a user