Загрузить файлы в «venv/Lib/site-packages/pip/_vendor/rich»
This commit is contained in:
93
venv/Lib/site-packages/pip/_vendor/rich/bar.py
Normal file
93
venv/Lib/site-packages/pip/_vendor/rich/bar.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from .color import Color
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
from .jupyter import JupyterMixin
|
||||
from .measure import Measurement
|
||||
from .segment import Segment
|
||||
from .style import Style
|
||||
|
||||
# There are left-aligned characters for 1/8 to 7/8, but
|
||||
# the right-aligned characters exist only for 1/8 and 4/8.
|
||||
BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
|
||||
END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
|
||||
FULL_BLOCK = "█"
|
||||
|
||||
|
||||
class Bar(JupyterMixin):
|
||||
"""Renders a solid block bar.
|
||||
|
||||
Args:
|
||||
size (float): Value for the end of the bar.
|
||||
begin (float): Begin point (between 0 and size, inclusive).
|
||||
end (float): End point (between 0 and size, inclusive).
|
||||
width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
|
||||
color (Union[Color, str], optional): Color of the bar. Defaults to "default".
|
||||
bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
size: float,
|
||||
begin: float,
|
||||
end: float,
|
||||
*,
|
||||
width: Optional[int] = None,
|
||||
color: Union[Color, str] = "default",
|
||||
bgcolor: Union[Color, str] = "default",
|
||||
):
|
||||
self.size = size
|
||||
self.begin = max(begin, 0)
|
||||
self.end = min(end, size)
|
||||
self.width = width
|
||||
self.style = Style(color=color, bgcolor=bgcolor)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Bar({self.size}, {self.begin}, {self.end})"
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
width = min(
|
||||
self.width if self.width is not None else options.max_width,
|
||||
options.max_width,
|
||||
)
|
||||
|
||||
if self.begin >= self.end:
|
||||
yield Segment(" " * width, self.style)
|
||||
yield Segment.line()
|
||||
return
|
||||
|
||||
prefix_complete_eights = int(width * 8 * self.begin / self.size)
|
||||
prefix_bar_count = prefix_complete_eights // 8
|
||||
prefix_eights_count = prefix_complete_eights % 8
|
||||
|
||||
body_complete_eights = int(width * 8 * self.end / self.size)
|
||||
body_bar_count = body_complete_eights // 8
|
||||
body_eights_count = body_complete_eights % 8
|
||||
|
||||
# When start and end fall into the same cell, we ideally should render
|
||||
# a symbol that's "center-aligned", but there is no good symbol in Unicode.
|
||||
# In this case, we fall back to right-aligned block symbol for simplicity.
|
||||
|
||||
prefix = " " * prefix_bar_count
|
||||
if prefix_eights_count:
|
||||
prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
|
||||
|
||||
body = FULL_BLOCK * body_bar_count
|
||||
if body_eights_count:
|
||||
body += END_BLOCK_ELEMENTS[body_eights_count]
|
||||
|
||||
suffix = " " * (width - len(body))
|
||||
|
||||
yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
|
||||
yield Segment.line()
|
||||
|
||||
def __rich_measure__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> Measurement:
|
||||
return (
|
||||
Measurement(self.width, self.width)
|
||||
if self.width is not None
|
||||
else Measurement(4, options.max_width)
|
||||
)
|
||||
474
venv/Lib/site-packages/pip/_vendor/rich/box.py
Normal file
474
venv/Lib/site-packages/pip/_vendor/rich/box.py
Normal file
@@ -0,0 +1,474 @@
|
||||
from typing import TYPE_CHECKING, Iterable, List, Literal
|
||||
|
||||
|
||||
from ._loop import loop_last
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._vendor.rich.console import ConsoleOptions
|
||||
|
||||
|
||||
class Box:
|
||||
"""Defines characters to render boxes.
|
||||
|
||||
┌─┬┐ top
|
||||
│ ││ head
|
||||
├─┼┤ head_row
|
||||
│ ││ mid
|
||||
├─┼┤ row
|
||||
├─┼┤ foot_row
|
||||
│ ││ foot
|
||||
└─┴┘ bottom
|
||||
|
||||
Args:
|
||||
box (str): Characters making up box.
|
||||
ascii (bool, optional): True if this box uses ascii characters only. Default is False.
|
||||
"""
|
||||
|
||||
def __init__(self, box: str, *, ascii: bool = False) -> None:
|
||||
self._box = box
|
||||
self.ascii = ascii
|
||||
line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines()
|
||||
# top
|
||||
self.top_left, self.top, self.top_divider, self.top_right = iter(line1)
|
||||
# head
|
||||
self.head_left, _, self.head_vertical, self.head_right = iter(line2)
|
||||
# head_row
|
||||
(
|
||||
self.head_row_left,
|
||||
self.head_row_horizontal,
|
||||
self.head_row_cross,
|
||||
self.head_row_right,
|
||||
) = iter(line3)
|
||||
|
||||
# mid
|
||||
self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4)
|
||||
# row
|
||||
self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5)
|
||||
# foot_row
|
||||
(
|
||||
self.foot_row_left,
|
||||
self.foot_row_horizontal,
|
||||
self.foot_row_cross,
|
||||
self.foot_row_right,
|
||||
) = iter(line6)
|
||||
# foot
|
||||
self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7)
|
||||
# bottom
|
||||
self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter(
|
||||
line8
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Box(...)"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._box
|
||||
|
||||
def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box":
|
||||
"""Substitute this box for another if it won't render due to platform issues.
|
||||
|
||||
Args:
|
||||
options (ConsoleOptions): Console options used in rendering.
|
||||
safe (bool, optional): Substitute this for another Box if there are known problems
|
||||
displaying on the platform (currently only relevant on Windows). Default is True.
|
||||
|
||||
Returns:
|
||||
Box: A different Box or the same Box.
|
||||
"""
|
||||
box = self
|
||||
if options.legacy_windows and safe:
|
||||
box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box)
|
||||
if options.ascii_only and not box.ascii:
|
||||
box = ASCII
|
||||
return box
|
||||
|
||||
def get_plain_headed_box(self) -> "Box":
|
||||
"""If this box uses special characters for the borders of the header, then
|
||||
return the equivalent box that does not.
|
||||
|
||||
Returns:
|
||||
Box: The most similar Box that doesn't use header-specific box characters.
|
||||
If the current Box already satisfies this criterion, then it's returned.
|
||||
"""
|
||||
return PLAIN_HEADED_SUBSTITUTIONS.get(self, self)
|
||||
|
||||
def get_top(self, widths: Iterable[int]) -> str:
|
||||
"""Get the top of a simple box.
|
||||
|
||||
Args:
|
||||
widths (List[int]): Widths of columns.
|
||||
|
||||
Returns:
|
||||
str: A string of box characters.
|
||||
"""
|
||||
|
||||
parts: List[str] = []
|
||||
append = parts.append
|
||||
append(self.top_left)
|
||||
for last, width in loop_last(widths):
|
||||
append(self.top * width)
|
||||
if not last:
|
||||
append(self.top_divider)
|
||||
append(self.top_right)
|
||||
return "".join(parts)
|
||||
|
||||
def get_row(
|
||||
self,
|
||||
widths: Iterable[int],
|
||||
level: Literal["head", "row", "foot", "mid"] = "row",
|
||||
edge: bool = True,
|
||||
) -> str:
|
||||
"""Get the top of a simple box.
|
||||
|
||||
Args:
|
||||
width (List[int]): Widths of columns.
|
||||
|
||||
Returns:
|
||||
str: A string of box characters.
|
||||
"""
|
||||
if level == "head":
|
||||
left = self.head_row_left
|
||||
horizontal = self.head_row_horizontal
|
||||
cross = self.head_row_cross
|
||||
right = self.head_row_right
|
||||
elif level == "row":
|
||||
left = self.row_left
|
||||
horizontal = self.row_horizontal
|
||||
cross = self.row_cross
|
||||
right = self.row_right
|
||||
elif level == "mid":
|
||||
left = self.mid_left
|
||||
horizontal = " "
|
||||
cross = self.mid_vertical
|
||||
right = self.mid_right
|
||||
elif level == "foot":
|
||||
left = self.foot_row_left
|
||||
horizontal = self.foot_row_horizontal
|
||||
cross = self.foot_row_cross
|
||||
right = self.foot_row_right
|
||||
else:
|
||||
raise ValueError("level must be 'head', 'row' or 'foot'")
|
||||
|
||||
parts: List[str] = []
|
||||
append = parts.append
|
||||
if edge:
|
||||
append(left)
|
||||
for last, width in loop_last(widths):
|
||||
append(horizontal * width)
|
||||
if not last:
|
||||
append(cross)
|
||||
if edge:
|
||||
append(right)
|
||||
return "".join(parts)
|
||||
|
||||
def get_bottom(self, widths: Iterable[int]) -> str:
|
||||
"""Get the bottom of a simple box.
|
||||
|
||||
Args:
|
||||
widths (List[int]): Widths of columns.
|
||||
|
||||
Returns:
|
||||
str: A string of box characters.
|
||||
"""
|
||||
|
||||
parts: List[str] = []
|
||||
append = parts.append
|
||||
append(self.bottom_left)
|
||||
for last, width in loop_last(widths):
|
||||
append(self.bottom * width)
|
||||
if not last:
|
||||
append(self.bottom_divider)
|
||||
append(self.bottom_right)
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
# fmt: off
|
||||
ASCII: Box = Box(
|
||||
"+--+\n"
|
||||
"| ||\n"
|
||||
"|-+|\n"
|
||||
"| ||\n"
|
||||
"|-+|\n"
|
||||
"|-+|\n"
|
||||
"| ||\n"
|
||||
"+--+\n",
|
||||
ascii=True,
|
||||
)
|
||||
|
||||
ASCII2: Box = Box(
|
||||
"+-++\n"
|
||||
"| ||\n"
|
||||
"+-++\n"
|
||||
"| ||\n"
|
||||
"+-++\n"
|
||||
"+-++\n"
|
||||
"| ||\n"
|
||||
"+-++\n",
|
||||
ascii=True,
|
||||
)
|
||||
|
||||
ASCII_DOUBLE_HEAD: Box = Box(
|
||||
"+-++\n"
|
||||
"| ||\n"
|
||||
"+=++\n"
|
||||
"| ||\n"
|
||||
"+-++\n"
|
||||
"+-++\n"
|
||||
"| ||\n"
|
||||
"+-++\n",
|
||||
ascii=True,
|
||||
)
|
||||
|
||||
SQUARE: Box = Box(
|
||||
"┌─┬┐\n"
|
||||
"│ ││\n"
|
||||
"├─┼┤\n"
|
||||
"│ ││\n"
|
||||
"├─┼┤\n"
|
||||
"├─┼┤\n"
|
||||
"│ ││\n"
|
||||
"└─┴┘\n"
|
||||
)
|
||||
|
||||
SQUARE_DOUBLE_HEAD: Box = Box(
|
||||
"┌─┬┐\n"
|
||||
"│ ││\n"
|
||||
"╞═╪╡\n"
|
||||
"│ ││\n"
|
||||
"├─┼┤\n"
|
||||
"├─┼┤\n"
|
||||
"│ ││\n"
|
||||
"└─┴┘\n"
|
||||
)
|
||||
|
||||
MINIMAL: Box = Box(
|
||||
" ╷ \n"
|
||||
" │ \n"
|
||||
"╶─┼╴\n"
|
||||
" │ \n"
|
||||
"╶─┼╴\n"
|
||||
"╶─┼╴\n"
|
||||
" │ \n"
|
||||
" ╵ \n"
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_HEAVY_HEAD: Box = Box(
|
||||
" ╷ \n"
|
||||
" │ \n"
|
||||
"╺━┿╸\n"
|
||||
" │ \n"
|
||||
"╶─┼╴\n"
|
||||
"╶─┼╴\n"
|
||||
" │ \n"
|
||||
" ╵ \n"
|
||||
)
|
||||
|
||||
MINIMAL_DOUBLE_HEAD: Box = Box(
|
||||
" ╷ \n"
|
||||
" │ \n"
|
||||
" ═╪ \n"
|
||||
" │ \n"
|
||||
" ─┼ \n"
|
||||
" ─┼ \n"
|
||||
" │ \n"
|
||||
" ╵ \n"
|
||||
)
|
||||
|
||||
|
||||
SIMPLE: Box = Box(
|
||||
" \n"
|
||||
" \n"
|
||||
" ── \n"
|
||||
" \n"
|
||||
" \n"
|
||||
" ── \n"
|
||||
" \n"
|
||||
" \n"
|
||||
)
|
||||
|
||||
SIMPLE_HEAD: Box = Box(
|
||||
" \n"
|
||||
" \n"
|
||||
" ── \n"
|
||||
" \n"
|
||||
" \n"
|
||||
" \n"
|
||||
" \n"
|
||||
" \n"
|
||||
)
|
||||
|
||||
|
||||
SIMPLE_HEAVY: Box = Box(
|
||||
" \n"
|
||||
" \n"
|
||||
" ━━ \n"
|
||||
" \n"
|
||||
" \n"
|
||||
" ━━ \n"
|
||||
" \n"
|
||||
" \n"
|
||||
)
|
||||
|
||||
|
||||
HORIZONTALS: Box = Box(
|
||||
" ── \n"
|
||||
" \n"
|
||||
" ── \n"
|
||||
" \n"
|
||||
" ── \n"
|
||||
" ── \n"
|
||||
" \n"
|
||||
" ── \n"
|
||||
)
|
||||
|
||||
ROUNDED: Box = Box(
|
||||
"╭─┬╮\n"
|
||||
"│ ││\n"
|
||||
"├─┼┤\n"
|
||||
"│ ││\n"
|
||||
"├─┼┤\n"
|
||||
"├─┼┤\n"
|
||||
"│ ││\n"
|
||||
"╰─┴╯\n"
|
||||
)
|
||||
|
||||
HEAVY: Box = Box(
|
||||
"┏━┳┓\n"
|
||||
"┃ ┃┃\n"
|
||||
"┣━╋┫\n"
|
||||
"┃ ┃┃\n"
|
||||
"┣━╋┫\n"
|
||||
"┣━╋┫\n"
|
||||
"┃ ┃┃\n"
|
||||
"┗━┻┛\n"
|
||||
)
|
||||
|
||||
HEAVY_EDGE: Box = Box(
|
||||
"┏━┯┓\n"
|
||||
"┃ │┃\n"
|
||||
"┠─┼┨\n"
|
||||
"┃ │┃\n"
|
||||
"┠─┼┨\n"
|
||||
"┠─┼┨\n"
|
||||
"┃ │┃\n"
|
||||
"┗━┷┛\n"
|
||||
)
|
||||
|
||||
HEAVY_HEAD: Box = Box(
|
||||
"┏━┳┓\n"
|
||||
"┃ ┃┃\n"
|
||||
"┡━╇┩\n"
|
||||
"│ ││\n"
|
||||
"├─┼┤\n"
|
||||
"├─┼┤\n"
|
||||
"│ ││\n"
|
||||
"└─┴┘\n"
|
||||
)
|
||||
|
||||
DOUBLE: Box = Box(
|
||||
"╔═╦╗\n"
|
||||
"║ ║║\n"
|
||||
"╠═╬╣\n"
|
||||
"║ ║║\n"
|
||||
"╠═╬╣\n"
|
||||
"╠═╬╣\n"
|
||||
"║ ║║\n"
|
||||
"╚═╩╝\n"
|
||||
)
|
||||
|
||||
DOUBLE_EDGE: Box = Box(
|
||||
"╔═╤╗\n"
|
||||
"║ │║\n"
|
||||
"╟─┼╢\n"
|
||||
"║ │║\n"
|
||||
"╟─┼╢\n"
|
||||
"╟─┼╢\n"
|
||||
"║ │║\n"
|
||||
"╚═╧╝\n"
|
||||
)
|
||||
|
||||
MARKDOWN: Box = Box(
|
||||
" \n"
|
||||
"| ||\n"
|
||||
"|-||\n"
|
||||
"| ||\n"
|
||||
"|-||\n"
|
||||
"|-||\n"
|
||||
"| ||\n"
|
||||
" \n",
|
||||
ascii=True,
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
# Map Boxes that don't render with raster fonts on to equivalent that do
|
||||
LEGACY_WINDOWS_SUBSTITUTIONS = {
|
||||
ROUNDED: SQUARE,
|
||||
MINIMAL_HEAVY_HEAD: MINIMAL,
|
||||
SIMPLE_HEAVY: SIMPLE,
|
||||
HEAVY: SQUARE,
|
||||
HEAVY_EDGE: SQUARE,
|
||||
HEAVY_HEAD: SQUARE,
|
||||
}
|
||||
|
||||
# Map headed boxes to their headerless equivalents
|
||||
PLAIN_HEADED_SUBSTITUTIONS = {
|
||||
HEAVY_HEAD: SQUARE,
|
||||
SQUARE_DOUBLE_HEAD: SQUARE,
|
||||
MINIMAL_DOUBLE_HEAD: MINIMAL,
|
||||
MINIMAL_HEAVY_HEAD: MINIMAL,
|
||||
ASCII_DOUBLE_HEAD: ASCII2,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from pip._vendor.rich.columns import Columns
|
||||
from pip._vendor.rich.panel import Panel
|
||||
|
||||
from . import box as box
|
||||
from .console import Console
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
||||
console = Console(record=True)
|
||||
|
||||
BOXES = [
|
||||
"ASCII",
|
||||
"ASCII2",
|
||||
"ASCII_DOUBLE_HEAD",
|
||||
"SQUARE",
|
||||
"SQUARE_DOUBLE_HEAD",
|
||||
"MINIMAL",
|
||||
"MINIMAL_HEAVY_HEAD",
|
||||
"MINIMAL_DOUBLE_HEAD",
|
||||
"SIMPLE",
|
||||
"SIMPLE_HEAD",
|
||||
"SIMPLE_HEAVY",
|
||||
"HORIZONTALS",
|
||||
"ROUNDED",
|
||||
"HEAVY",
|
||||
"HEAVY_EDGE",
|
||||
"HEAVY_HEAD",
|
||||
"DOUBLE",
|
||||
"DOUBLE_EDGE",
|
||||
"MARKDOWN",
|
||||
]
|
||||
|
||||
console.print(Panel("[bold green]Box Constants", style="green"), justify="center")
|
||||
console.print()
|
||||
|
||||
columns = Columns(expand=True, padding=2)
|
||||
for box_name in sorted(BOXES):
|
||||
table = Table(
|
||||
show_footer=True, style="dim", border_style="not dim", expand=True
|
||||
)
|
||||
table.add_column("Header 1", "Footer 1")
|
||||
table.add_column("Header 2", "Footer 2")
|
||||
table.add_row("Cell", "Cell")
|
||||
table.add_row("Cell", "Cell")
|
||||
table.box = getattr(box, box_name)
|
||||
table.title = Text(f"box.{box_name}", style="magenta")
|
||||
columns.add_renderable(table)
|
||||
console.print(columns)
|
||||
|
||||
# console.save_svg("box.svg")
|
||||
174
venv/Lib/site-packages/pip/_vendor/rich/cells.py
Normal file
174
venv/Lib/site-packages/pip/_vendor/rich/cells.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Callable
|
||||
|
||||
from ._cell_widths import CELL_WIDTHS
|
||||
|
||||
# Ranges of unicode ordinals that produce a 1-cell wide character
|
||||
# This is non-exhaustive, but covers most common Western characters
|
||||
_SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [
|
||||
(0x20, 0x7E), # Latin (excluding non-printable)
|
||||
(0xA0, 0xAC),
|
||||
(0xAE, 0x002FF),
|
||||
(0x00370, 0x00482), # Greek / Cyrillic
|
||||
(0x02500, 0x025FC), # Box drawing, box elements, geometric shapes
|
||||
(0x02800, 0x028FF), # Braille
|
||||
]
|
||||
|
||||
# A set of characters that are a single cell wide
|
||||
_SINGLE_CELLS = frozenset(
|
||||
[
|
||||
character
|
||||
for _start, _end in _SINGLE_CELL_UNICODE_RANGES
|
||||
for character in map(chr, range(_start, _end + 1))
|
||||
]
|
||||
)
|
||||
|
||||
# When called with a string this will return True if all
|
||||
# characters are single-cell, otherwise False
|
||||
_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset
|
||||
|
||||
|
||||
@lru_cache(4096)
|
||||
def cached_cell_len(text: str) -> int:
|
||||
"""Get the number of cells required to display text.
|
||||
|
||||
This method always caches, which may use up a lot of memory. It is recommended to use
|
||||
`cell_len` over this method.
|
||||
|
||||
Args:
|
||||
text (str): Text to display.
|
||||
|
||||
Returns:
|
||||
int: Get the number of cells required to display text.
|
||||
"""
|
||||
if _is_single_cell_widths(text):
|
||||
return len(text)
|
||||
return sum(map(get_character_cell_size, text))
|
||||
|
||||
|
||||
def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int:
|
||||
"""Get the number of cells required to display text.
|
||||
|
||||
Args:
|
||||
text (str): Text to display.
|
||||
|
||||
Returns:
|
||||
int: Get the number of cells required to display text.
|
||||
"""
|
||||
if len(text) < 512:
|
||||
return _cell_len(text)
|
||||
if _is_single_cell_widths(text):
|
||||
return len(text)
|
||||
return sum(map(get_character_cell_size, text))
|
||||
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def get_character_cell_size(character: str) -> int:
|
||||
"""Get the cell size of a character.
|
||||
|
||||
Args:
|
||||
character (str): A single character.
|
||||
|
||||
Returns:
|
||||
int: Number of cells (0, 1 or 2) occupied by that character.
|
||||
"""
|
||||
codepoint = ord(character)
|
||||
_table = CELL_WIDTHS
|
||||
lower_bound = 0
|
||||
upper_bound = len(_table) - 1
|
||||
index = (lower_bound + upper_bound) // 2
|
||||
while True:
|
||||
start, end, width = _table[index]
|
||||
if codepoint < start:
|
||||
upper_bound = index - 1
|
||||
elif codepoint > end:
|
||||
lower_bound = index + 1
|
||||
else:
|
||||
return 0 if width == -1 else width
|
||||
if upper_bound < lower_bound:
|
||||
break
|
||||
index = (lower_bound + upper_bound) // 2
|
||||
return 1
|
||||
|
||||
|
||||
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 total <= 0:
|
||||
return ""
|
||||
cell_size = cell_len(text)
|
||||
if cell_size == total:
|
||||
return text
|
||||
if cell_size < total:
|
||||
return text + " " * (total - cell_size)
|
||||
|
||||
start = 0
|
||||
end = len(text)
|
||||
|
||||
# Binary search until we find the right size
|
||||
while True:
|
||||
pos = (start + end) // 2
|
||||
before = text[: pos + 1]
|
||||
before_len = cell_len(before)
|
||||
if before_len == total + 1 and cell_len(before[-1]) == 2:
|
||||
return before[:-1] + " "
|
||||
if before_len == total:
|
||||
return before
|
||||
if before_len > total:
|
||||
end = pos
|
||||
else:
|
||||
start = pos
|
||||
|
||||
|
||||
def chop_cells(
|
||||
text: str,
|
||||
width: int,
|
||||
) -> list[str]:
|
||||
"""Split text into lines such that each line fits within the available (cell) width.
|
||||
|
||||
Args:
|
||||
text: The text to fold such that it fits in the given width.
|
||||
width: The width available (number of cells).
|
||||
|
||||
Returns:
|
||||
A list of strings such that each string in the list has cell width
|
||||
less than or equal to the available width.
|
||||
"""
|
||||
_get_character_cell_size = get_character_cell_size
|
||||
lines: list[list[str]] = [[]]
|
||||
|
||||
append_new_line = lines.append
|
||||
append_to_last_line = lines[-1].append
|
||||
|
||||
total_width = 0
|
||||
|
||||
for character in text:
|
||||
cell_width = _get_character_cell_size(character)
|
||||
char_doesnt_fit = total_width + cell_width > width
|
||||
|
||||
if char_doesnt_fit:
|
||||
append_new_line([character])
|
||||
append_to_last_line = lines[-1].append
|
||||
total_width = cell_width
|
||||
else:
|
||||
append_to_last_line(character)
|
||||
total_width += cell_width
|
||||
|
||||
return ["".join(line) for line in lines]
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
print(get_character_cell_size("😽"))
|
||||
for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8):
|
||||
print(line)
|
||||
for n in range(80, 1, -1):
|
||||
print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|")
|
||||
print("x" * n)
|
||||
621
venv/Lib/site-packages/pip/_vendor/rich/color.py
Normal file
621
venv/Lib/site-packages/pip/_vendor/rich/color.py
Normal file
@@ -0,0 +1,621 @@
|
||||
import re
|
||||
import sys
|
||||
from colorsys import rgb_to_hls
|
||||
from enum import IntEnum
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple
|
||||
|
||||
from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE
|
||||
from .color_triplet import ColorTriplet
|
||||
from .repr import Result, rich_repr
|
||||
from .terminal_theme import DEFAULT_TERMINAL_THEME
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .terminal_theme import TerminalTheme
|
||||
from .text import Text
|
||||
|
||||
|
||||
WINDOWS = sys.platform == "win32"
|
||||
|
||||
|
||||
class ColorSystem(IntEnum):
|
||||
"""One of the 3 color system supported by terminals."""
|
||||
|
||||
STANDARD = 1
|
||||
EIGHT_BIT = 2
|
||||
TRUECOLOR = 3
|
||||
WINDOWS = 4
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ColorSystem.{self.name}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
|
||||
class ColorType(IntEnum):
|
||||
"""Type of color stored in Color class."""
|
||||
|
||||
DEFAULT = 0
|
||||
STANDARD = 1
|
||||
EIGHT_BIT = 2
|
||||
TRUECOLOR = 3
|
||||
WINDOWS = 4
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ColorType.{self.name}"
|
||||
|
||||
|
||||
ANSI_COLOR_NAMES = {
|
||||
"black": 0,
|
||||
"red": 1,
|
||||
"green": 2,
|
||||
"yellow": 3,
|
||||
"blue": 4,
|
||||
"magenta": 5,
|
||||
"cyan": 6,
|
||||
"white": 7,
|
||||
"bright_black": 8,
|
||||
"bright_red": 9,
|
||||
"bright_green": 10,
|
||||
"bright_yellow": 11,
|
||||
"bright_blue": 12,
|
||||
"bright_magenta": 13,
|
||||
"bright_cyan": 14,
|
||||
"bright_white": 15,
|
||||
"grey0": 16,
|
||||
"gray0": 16,
|
||||
"navy_blue": 17,
|
||||
"dark_blue": 18,
|
||||
"blue3": 20,
|
||||
"blue1": 21,
|
||||
"dark_green": 22,
|
||||
"deep_sky_blue4": 25,
|
||||
"dodger_blue3": 26,
|
||||
"dodger_blue2": 27,
|
||||
"green4": 28,
|
||||
"spring_green4": 29,
|
||||
"turquoise4": 30,
|
||||
"deep_sky_blue3": 32,
|
||||
"dodger_blue1": 33,
|
||||
"green3": 40,
|
||||
"spring_green3": 41,
|
||||
"dark_cyan": 36,
|
||||
"light_sea_green": 37,
|
||||
"deep_sky_blue2": 38,
|
||||
"deep_sky_blue1": 39,
|
||||
"spring_green2": 47,
|
||||
"cyan3": 43,
|
||||
"dark_turquoise": 44,
|
||||
"turquoise2": 45,
|
||||
"green1": 46,
|
||||
"spring_green1": 48,
|
||||
"medium_spring_green": 49,
|
||||
"cyan2": 50,
|
||||
"cyan1": 51,
|
||||
"dark_red": 88,
|
||||
"deep_pink4": 125,
|
||||
"purple4": 55,
|
||||
"purple3": 56,
|
||||
"blue_violet": 57,
|
||||
"orange4": 94,
|
||||
"grey37": 59,
|
||||
"gray37": 59,
|
||||
"medium_purple4": 60,
|
||||
"slate_blue3": 62,
|
||||
"royal_blue1": 63,
|
||||
"chartreuse4": 64,
|
||||
"dark_sea_green4": 71,
|
||||
"pale_turquoise4": 66,
|
||||
"steel_blue": 67,
|
||||
"steel_blue3": 68,
|
||||
"cornflower_blue": 69,
|
||||
"chartreuse3": 76,
|
||||
"cadet_blue": 73,
|
||||
"sky_blue3": 74,
|
||||
"steel_blue1": 81,
|
||||
"pale_green3": 114,
|
||||
"sea_green3": 78,
|
||||
"aquamarine3": 79,
|
||||
"medium_turquoise": 80,
|
||||
"chartreuse2": 112,
|
||||
"sea_green2": 83,
|
||||
"sea_green1": 85,
|
||||
"aquamarine1": 122,
|
||||
"dark_slate_gray2": 87,
|
||||
"dark_magenta": 91,
|
||||
"dark_violet": 128,
|
||||
"purple": 129,
|
||||
"light_pink4": 95,
|
||||
"plum4": 96,
|
||||
"medium_purple3": 98,
|
||||
"slate_blue1": 99,
|
||||
"yellow4": 106,
|
||||
"wheat4": 101,
|
||||
"grey53": 102,
|
||||
"gray53": 102,
|
||||
"light_slate_grey": 103,
|
||||
"light_slate_gray": 103,
|
||||
"medium_purple": 104,
|
||||
"light_slate_blue": 105,
|
||||
"dark_olive_green3": 149,
|
||||
"dark_sea_green": 108,
|
||||
"light_sky_blue3": 110,
|
||||
"sky_blue2": 111,
|
||||
"dark_sea_green3": 150,
|
||||
"dark_slate_gray3": 116,
|
||||
"sky_blue1": 117,
|
||||
"chartreuse1": 118,
|
||||
"light_green": 120,
|
||||
"pale_green1": 156,
|
||||
"dark_slate_gray1": 123,
|
||||
"red3": 160,
|
||||
"medium_violet_red": 126,
|
||||
"magenta3": 164,
|
||||
"dark_orange3": 166,
|
||||
"indian_red": 167,
|
||||
"hot_pink3": 168,
|
||||
"medium_orchid3": 133,
|
||||
"medium_orchid": 134,
|
||||
"medium_purple2": 140,
|
||||
"dark_goldenrod": 136,
|
||||
"light_salmon3": 173,
|
||||
"rosy_brown": 138,
|
||||
"grey63": 139,
|
||||
"gray63": 139,
|
||||
"medium_purple1": 141,
|
||||
"gold3": 178,
|
||||
"dark_khaki": 143,
|
||||
"navajo_white3": 144,
|
||||
"grey69": 145,
|
||||
"gray69": 145,
|
||||
"light_steel_blue3": 146,
|
||||
"light_steel_blue": 147,
|
||||
"yellow3": 184,
|
||||
"dark_sea_green2": 157,
|
||||
"light_cyan3": 152,
|
||||
"light_sky_blue1": 153,
|
||||
"green_yellow": 154,
|
||||
"dark_olive_green2": 155,
|
||||
"dark_sea_green1": 193,
|
||||
"pale_turquoise1": 159,
|
||||
"deep_pink3": 162,
|
||||
"magenta2": 200,
|
||||
"hot_pink2": 169,
|
||||
"orchid": 170,
|
||||
"medium_orchid1": 207,
|
||||
"orange3": 172,
|
||||
"light_pink3": 174,
|
||||
"pink3": 175,
|
||||
"plum3": 176,
|
||||
"violet": 177,
|
||||
"light_goldenrod3": 179,
|
||||
"tan": 180,
|
||||
"misty_rose3": 181,
|
||||
"thistle3": 182,
|
||||
"plum2": 183,
|
||||
"khaki3": 185,
|
||||
"light_goldenrod2": 222,
|
||||
"light_yellow3": 187,
|
||||
"grey84": 188,
|
||||
"gray84": 188,
|
||||
"light_steel_blue1": 189,
|
||||
"yellow2": 190,
|
||||
"dark_olive_green1": 192,
|
||||
"honeydew2": 194,
|
||||
"light_cyan1": 195,
|
||||
"red1": 196,
|
||||
"deep_pink2": 197,
|
||||
"deep_pink1": 199,
|
||||
"magenta1": 201,
|
||||
"orange_red1": 202,
|
||||
"indian_red1": 204,
|
||||
"hot_pink": 206,
|
||||
"dark_orange": 208,
|
||||
"salmon1": 209,
|
||||
"light_coral": 210,
|
||||
"pale_violet_red1": 211,
|
||||
"orchid2": 212,
|
||||
"orchid1": 213,
|
||||
"orange1": 214,
|
||||
"sandy_brown": 215,
|
||||
"light_salmon1": 216,
|
||||
"light_pink1": 217,
|
||||
"pink1": 218,
|
||||
"plum1": 219,
|
||||
"gold1": 220,
|
||||
"navajo_white1": 223,
|
||||
"misty_rose1": 224,
|
||||
"thistle1": 225,
|
||||
"yellow1": 226,
|
||||
"light_goldenrod1": 227,
|
||||
"khaki1": 228,
|
||||
"wheat1": 229,
|
||||
"cornsilk1": 230,
|
||||
"grey100": 231,
|
||||
"gray100": 231,
|
||||
"grey3": 232,
|
||||
"gray3": 232,
|
||||
"grey7": 233,
|
||||
"gray7": 233,
|
||||
"grey11": 234,
|
||||
"gray11": 234,
|
||||
"grey15": 235,
|
||||
"gray15": 235,
|
||||
"grey19": 236,
|
||||
"gray19": 236,
|
||||
"grey23": 237,
|
||||
"gray23": 237,
|
||||
"grey27": 238,
|
||||
"gray27": 238,
|
||||
"grey30": 239,
|
||||
"gray30": 239,
|
||||
"grey35": 240,
|
||||
"gray35": 240,
|
||||
"grey39": 241,
|
||||
"gray39": 241,
|
||||
"grey42": 242,
|
||||
"gray42": 242,
|
||||
"grey46": 243,
|
||||
"gray46": 243,
|
||||
"grey50": 244,
|
||||
"gray50": 244,
|
||||
"grey54": 245,
|
||||
"gray54": 245,
|
||||
"grey58": 246,
|
||||
"gray58": 246,
|
||||
"grey62": 247,
|
||||
"gray62": 247,
|
||||
"grey66": 248,
|
||||
"gray66": 248,
|
||||
"grey70": 249,
|
||||
"gray70": 249,
|
||||
"grey74": 250,
|
||||
"gray74": 250,
|
||||
"grey78": 251,
|
||||
"gray78": 251,
|
||||
"grey82": 252,
|
||||
"gray82": 252,
|
||||
"grey85": 253,
|
||||
"gray85": 253,
|
||||
"grey89": 254,
|
||||
"gray89": 254,
|
||||
"grey93": 255,
|
||||
"gray93": 255,
|
||||
}
|
||||
|
||||
|
||||
class ColorParseError(Exception):
|
||||
"""The color could not be parsed."""
|
||||
|
||||
|
||||
RE_COLOR = re.compile(
|
||||
r"""^
|
||||
\#([0-9a-f]{6})$|
|
||||
color\(([0-9]{1,3})\)$|
|
||||
rgb\(([\d\s,]+)\)$
|
||||
""",
|
||||
re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
@rich_repr
|
||||
class Color(NamedTuple):
|
||||
"""Terminal color definition."""
|
||||
|
||||
name: str
|
||||
"""The name of the color (typically the input to Color.parse)."""
|
||||
type: ColorType
|
||||
"""The type of the color."""
|
||||
number: Optional[int] = None
|
||||
"""The color number, if a standard color, or None."""
|
||||
triplet: Optional[ColorTriplet] = None
|
||||
"""A triplet of color components, if an RGB color."""
|
||||
|
||||
def __rich__(self) -> "Text":
|
||||
"""Displays the actual color if Rich printed."""
|
||||
from .style import Style
|
||||
from .text import Text
|
||||
|
||||
return Text.assemble(
|
||||
f"<color {self.name!r} ({self.type.name.lower()})",
|
||||
("⬤", Style(color=self)),
|
||||
" >",
|
||||
)
|
||||
|
||||
def __rich_repr__(self) -> Result:
|
||||
yield self.name
|
||||
yield self.type
|
||||
yield "number", self.number, None
|
||||
yield "triplet", self.triplet, None
|
||||
|
||||
@property
|
||||
def system(self) -> ColorSystem:
|
||||
"""Get the native color system for this color."""
|
||||
if self.type == ColorType.DEFAULT:
|
||||
return ColorSystem.STANDARD
|
||||
return ColorSystem(int(self.type))
|
||||
|
||||
@property
|
||||
def is_system_defined(self) -> bool:
|
||||
"""Check if the color is ultimately defined by the system."""
|
||||
return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR)
|
||||
|
||||
@property
|
||||
def is_default(self) -> bool:
|
||||
"""Check if the color is a default color."""
|
||||
return self.type == ColorType.DEFAULT
|
||||
|
||||
def get_truecolor(
|
||||
self, theme: Optional["TerminalTheme"] = None, foreground: bool = True
|
||||
) -> ColorTriplet:
|
||||
"""Get an equivalent color triplet for this color.
|
||||
|
||||
Args:
|
||||
theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None.
|
||||
foreground (bool, optional): True for a foreground color, or False for background. Defaults to True.
|
||||
|
||||
Returns:
|
||||
ColorTriplet: A color triplet containing RGB components.
|
||||
"""
|
||||
|
||||
if theme is None:
|
||||
theme = DEFAULT_TERMINAL_THEME
|
||||
if self.type == ColorType.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
return self.triplet
|
||||
elif self.type == ColorType.EIGHT_BIT:
|
||||
assert self.number is not None
|
||||
return EIGHT_BIT_PALETTE[self.number]
|
||||
elif self.type == ColorType.STANDARD:
|
||||
assert self.number is not None
|
||||
return theme.ansi_colors[self.number]
|
||||
elif self.type == ColorType.WINDOWS:
|
||||
assert self.number is not None
|
||||
return WINDOWS_PALETTE[self.number]
|
||||
else: # self.type == ColorType.DEFAULT:
|
||||
assert self.number is None
|
||||
return theme.foreground_color if foreground else theme.background_color
|
||||
|
||||
@classmethod
|
||||
def from_ansi(cls, number: int) -> "Color":
|
||||
"""Create a Color number from it's 8-bit ansi number.
|
||||
|
||||
Args:
|
||||
number (int): A number between 0-255 inclusive.
|
||||
|
||||
Returns:
|
||||
Color: A new Color instance.
|
||||
"""
|
||||
return cls(
|
||||
name=f"color({number})",
|
||||
type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
|
||||
number=number,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_triplet(cls, triplet: "ColorTriplet") -> "Color":
|
||||
"""Create a truecolor RGB color from a triplet of values.
|
||||
|
||||
Args:
|
||||
triplet (ColorTriplet): A color triplet containing red, green and blue components.
|
||||
|
||||
Returns:
|
||||
Color: A new color object.
|
||||
"""
|
||||
return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
@classmethod
|
||||
def from_rgb(cls, red: float, green: float, blue: float) -> "Color":
|
||||
"""Create a truecolor from three color components in the range(0->255).
|
||||
|
||||
Args:
|
||||
red (float): Red component in range 0-255.
|
||||
green (float): Green component in range 0-255.
|
||||
blue (float): Blue component in range 0-255.
|
||||
|
||||
Returns:
|
||||
Color: A new color object.
|
||||
"""
|
||||
return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue)))
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "Color":
|
||||
"""Get a Color instance representing the default color.
|
||||
|
||||
Returns:
|
||||
Color: Default color.
|
||||
"""
|
||||
return cls(name="default", type=ColorType.DEFAULT)
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=1024)
|
||||
def parse(cls, color: str) -> "Color":
|
||||
"""Parse a color definition."""
|
||||
original_color = color
|
||||
color = color.lower().strip()
|
||||
|
||||
if color == "default":
|
||||
return cls(color, type=ColorType.DEFAULT)
|
||||
|
||||
color_number = ANSI_COLOR_NAMES.get(color)
|
||||
if color_number is not None:
|
||||
return cls(
|
||||
color,
|
||||
type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT),
|
||||
number=color_number,
|
||||
)
|
||||
|
||||
color_match = RE_COLOR.match(color)
|
||||
if color_match is None:
|
||||
raise ColorParseError(f"{original_color!r} is not a valid color")
|
||||
|
||||
color_24, color_8, color_rgb = color_match.groups()
|
||||
if color_24:
|
||||
triplet = ColorTriplet(
|
||||
int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16)
|
||||
)
|
||||
return cls(color, ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
elif color_8:
|
||||
number = int(color_8)
|
||||
if number > 255:
|
||||
raise ColorParseError(f"color number must be <= 255 in {color!r}")
|
||||
return cls(
|
||||
color,
|
||||
type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
|
||||
number=number,
|
||||
)
|
||||
|
||||
else: # color_rgb:
|
||||
components = color_rgb.split(",")
|
||||
if len(components) != 3:
|
||||
raise ColorParseError(
|
||||
f"expected three components in {original_color!r}"
|
||||
)
|
||||
red, green, blue = components
|
||||
triplet = ColorTriplet(int(red), int(green), int(blue))
|
||||
if not all(component <= 255 for component in triplet):
|
||||
raise ColorParseError(
|
||||
f"color components must be <= 255 in {original_color!r}"
|
||||
)
|
||||
return cls(color, ColorType.TRUECOLOR, triplet=triplet)
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]:
|
||||
"""Get the ANSI escape codes for this color."""
|
||||
_type = self.type
|
||||
if _type == ColorType.DEFAULT:
|
||||
return ("39" if foreground else "49",)
|
||||
|
||||
elif _type == ColorType.WINDOWS:
|
||||
number = self.number
|
||||
assert number is not None
|
||||
fore, back = (30, 40) if number < 8 else (82, 92)
|
||||
return (str(fore + number if foreground else back + number),)
|
||||
|
||||
elif _type == ColorType.STANDARD:
|
||||
number = self.number
|
||||
assert number is not None
|
||||
fore, back = (30, 40) if number < 8 else (82, 92)
|
||||
return (str(fore + number if foreground else back + number),)
|
||||
|
||||
elif _type == ColorType.EIGHT_BIT:
|
||||
assert self.number is not None
|
||||
return ("38" if foreground else "48", "5", str(self.number))
|
||||
|
||||
else: # self.standard == ColorStandard.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
red, green, blue = self.triplet
|
||||
return ("38" if foreground else "48", "2", str(red), str(green), str(blue))
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def downgrade(self, system: ColorSystem) -> "Color":
|
||||
"""Downgrade a color system to a system with fewer colors."""
|
||||
|
||||
if self.type in (ColorType.DEFAULT, system):
|
||||
return self
|
||||
# Convert to 8-bit color from truecolor color
|
||||
if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
_h, l, s = rgb_to_hls(*self.triplet.normalized)
|
||||
# If saturation is under 15% assume it is grayscale
|
||||
if s < 0.15:
|
||||
gray = round(l * 25.0)
|
||||
if gray == 0:
|
||||
color_number = 16
|
||||
elif gray == 25:
|
||||
color_number = 231
|
||||
else:
|
||||
color_number = 231 + gray
|
||||
return Color(self.name, ColorType.EIGHT_BIT, number=color_number)
|
||||
|
||||
red, green, blue = self.triplet
|
||||
six_red = red / 95 if red < 95 else 1 + (red - 95) / 40
|
||||
six_green = green / 95 if green < 95 else 1 + (green - 95) / 40
|
||||
six_blue = blue / 95 if blue < 95 else 1 + (blue - 95) / 40
|
||||
|
||||
color_number = (
|
||||
16 + 36 * round(six_red) + 6 * round(six_green) + round(six_blue)
|
||||
)
|
||||
return Color(self.name, ColorType.EIGHT_BIT, number=color_number)
|
||||
|
||||
# Convert to standard from truecolor or 8-bit
|
||||
elif system == ColorSystem.STANDARD:
|
||||
if self.system == ColorSystem.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
triplet = self.triplet
|
||||
else: # self.system == ColorSystem.EIGHT_BIT
|
||||
assert self.number is not None
|
||||
triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])
|
||||
|
||||
color_number = STANDARD_PALETTE.match(triplet)
|
||||
return Color(self.name, ColorType.STANDARD, number=color_number)
|
||||
|
||||
elif system == ColorSystem.WINDOWS:
|
||||
if self.system == ColorSystem.TRUECOLOR:
|
||||
assert self.triplet is not None
|
||||
triplet = self.triplet
|
||||
else: # self.system == ColorSystem.EIGHT_BIT
|
||||
assert self.number is not None
|
||||
if self.number < 16:
|
||||
return Color(self.name, ColorType.WINDOWS, number=self.number)
|
||||
triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number])
|
||||
|
||||
color_number = WINDOWS_PALETTE.match(triplet)
|
||||
return Color(self.name, ColorType.WINDOWS, number=color_number)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def parse_rgb_hex(hex_color: str) -> ColorTriplet:
|
||||
"""Parse six hex characters in to RGB triplet."""
|
||||
assert len(hex_color) == 6, "must be 6 characters"
|
||||
color = ColorTriplet(
|
||||
int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
||||
)
|
||||
return color
|
||||
|
||||
|
||||
def blend_rgb(
|
||||
color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5
|
||||
) -> ColorTriplet:
|
||||
"""Blend one RGB color in to another."""
|
||||
r1, g1, b1 = color1
|
||||
r2, g2, b2 = color2
|
||||
new_color = ColorTriplet(
|
||||
int(r1 + (r2 - r1) * cross_fade),
|
||||
int(g1 + (g2 - g1) * cross_fade),
|
||||
int(b1 + (b2 - b1) * cross_fade),
|
||||
)
|
||||
return new_color
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
from .console import Console
|
||||
from .table import Table
|
||||
from .text import Text
|
||||
|
||||
console = Console()
|
||||
|
||||
table = Table(show_footer=False, show_edge=True)
|
||||
table.add_column("Color", width=10, overflow="ellipsis")
|
||||
table.add_column("Number", justify="right", style="yellow")
|
||||
table.add_column("Name", style="green")
|
||||
table.add_column("Hex", style="blue")
|
||||
table.add_column("RGB", style="magenta")
|
||||
|
||||
colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items())
|
||||
for color_number, name in colors:
|
||||
if "grey" in name:
|
||||
continue
|
||||
color_cell = Text(" " * 10, style=f"on {name}")
|
||||
if color_number < 16:
|
||||
table.add_row(color_cell, f"{color_number}", Text(f'"{name}"'))
|
||||
else:
|
||||
color = EIGHT_BIT_PALETTE[color_number] # type: ignore[has-type]
|
||||
table.add_row(
|
||||
color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
38
venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py
Normal file
38
venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from typing import NamedTuple, Tuple
|
||||
|
||||
|
||||
class ColorTriplet(NamedTuple):
|
||||
"""The red, green, and blue components of a color."""
|
||||
|
||||
red: int
|
||||
"""Red component in 0 to 255 range."""
|
||||
green: int
|
||||
"""Green component in 0 to 255 range."""
|
||||
blue: int
|
||||
"""Blue component in 0 to 255 range."""
|
||||
|
||||
@property
|
||||
def hex(self) -> str:
|
||||
"""get the color triplet in CSS style."""
|
||||
red, green, blue = self
|
||||
return f"#{red:02x}{green:02x}{blue:02x}"
|
||||
|
||||
@property
|
||||
def rgb(self) -> str:
|
||||
"""The color in RGB format.
|
||||
|
||||
Returns:
|
||||
str: An rgb color, e.g. ``"rgb(100,23,255)"``.
|
||||
"""
|
||||
red, green, blue = self
|
||||
return f"rgb({red},{green},{blue})"
|
||||
|
||||
@property
|
||||
def normalized(self) -> Tuple[float, float, float]:
|
||||
"""Convert components into floats between 0 and 1.
|
||||
|
||||
Returns:
|
||||
Tuple[float, float, float]: A tuple of three normalized colour components.
|
||||
"""
|
||||
red, green, blue = self
|
||||
return red / 255.0, green / 255.0, blue / 255.0
|
||||
Reference in New Issue
Block a user