Загрузить файлы в «venv/Lib/site-packages/pip/_vendor/rich»
This commit is contained in:
56
venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py
Normal file
56
venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from typing import Iterable, Sequence, Tuple, cast
|
||||||
|
|
||||||
|
from pip._vendor.rich._win32_console import LegacyWindowsTerm, WindowsCoordinates
|
||||||
|
from pip._vendor.rich.segment import ControlCode, ControlType, Segment
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None:
|
||||||
|
"""Makes appropriate Windows Console API calls based on the segments in the buffer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls.
|
||||||
|
term (LegacyWindowsTerm): Used to call the Windows Console API.
|
||||||
|
"""
|
||||||
|
for text, style, control in buffer:
|
||||||
|
if not control:
|
||||||
|
if style:
|
||||||
|
term.write_styled(text, style)
|
||||||
|
else:
|
||||||
|
term.write_text(text)
|
||||||
|
else:
|
||||||
|
control_codes: Sequence[ControlCode] = control
|
||||||
|
for control_code in control_codes:
|
||||||
|
control_type = control_code[0]
|
||||||
|
if control_type == ControlType.CURSOR_MOVE_TO:
|
||||||
|
_, x, y = cast(Tuple[ControlType, int, int], control_code)
|
||||||
|
term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1))
|
||||||
|
elif control_type == ControlType.CARRIAGE_RETURN:
|
||||||
|
term.write_text("\r")
|
||||||
|
elif control_type == ControlType.HOME:
|
||||||
|
term.move_cursor_to(WindowsCoordinates(0, 0))
|
||||||
|
elif control_type == ControlType.CURSOR_UP:
|
||||||
|
term.move_cursor_up()
|
||||||
|
elif control_type == ControlType.CURSOR_DOWN:
|
||||||
|
term.move_cursor_down()
|
||||||
|
elif control_type == ControlType.CURSOR_FORWARD:
|
||||||
|
term.move_cursor_forward()
|
||||||
|
elif control_type == ControlType.CURSOR_BACKWARD:
|
||||||
|
term.move_cursor_backward()
|
||||||
|
elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN:
|
||||||
|
_, column = cast(Tuple[ControlType, int], control_code)
|
||||||
|
term.move_cursor_to_column(column - 1)
|
||||||
|
elif control_type == ControlType.HIDE_CURSOR:
|
||||||
|
term.hide_cursor()
|
||||||
|
elif control_type == ControlType.SHOW_CURSOR:
|
||||||
|
term.show_cursor()
|
||||||
|
elif control_type == ControlType.ERASE_IN_LINE:
|
||||||
|
_, mode = cast(Tuple[ControlType, int], control_code)
|
||||||
|
if mode == 0:
|
||||||
|
term.erase_end_of_line()
|
||||||
|
elif mode == 1:
|
||||||
|
term.erase_start_of_line()
|
||||||
|
elif mode == 2:
|
||||||
|
term.erase_line()
|
||||||
|
elif control_type == ControlType.SET_WINDOW_TITLE:
|
||||||
|
_, title = cast(Tuple[ControlType, str], control_code)
|
||||||
|
term.set_title(title)
|
||||||
93
venv/Lib/site-packages/pip/_vendor/rich/_wrap.py
Normal file
93
venv/Lib/site-packages/pip/_vendor/rich/_wrap.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from ._loop import loop_last
|
||||||
|
from .cells import cell_len, chop_cells
|
||||||
|
|
||||||
|
re_word = re.compile(r"\s*\S+\s*")
|
||||||
|
|
||||||
|
|
||||||
|
def words(text: str) -> Iterable[tuple[int, int, str]]:
|
||||||
|
"""Yields each word from the text as a tuple
|
||||||
|
containing (start_index, end_index, word). A "word" in this context may
|
||||||
|
include the actual word and any whitespace to the right.
|
||||||
|
"""
|
||||||
|
position = 0
|
||||||
|
word_match = re_word.match(text, position)
|
||||||
|
while word_match is not None:
|
||||||
|
start, end = word_match.span()
|
||||||
|
word = word_match.group(0)
|
||||||
|
yield start, end, word
|
||||||
|
word_match = re_word.match(text, end)
|
||||||
|
|
||||||
|
|
||||||
|
def divide_line(text: str, width: int, fold: bool = True) -> list[int]:
|
||||||
|
"""Given a string of text, and a width (measured in cells), return a list
|
||||||
|
of cell offsets which the string should be split at in order for it to fit
|
||||||
|
within the given width.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The text to examine.
|
||||||
|
width: The available cell width.
|
||||||
|
fold: If True, words longer than `width` will be folded onto a new line.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of indices to break the line at.
|
||||||
|
"""
|
||||||
|
break_positions: list[int] = [] # offsets to insert the breaks at
|
||||||
|
append = break_positions.append
|
||||||
|
cell_offset = 0
|
||||||
|
_cell_len = cell_len
|
||||||
|
|
||||||
|
for start, _end, word in words(text):
|
||||||
|
word_length = _cell_len(word.rstrip())
|
||||||
|
remaining_space = width - cell_offset
|
||||||
|
word_fits_remaining_space = remaining_space >= word_length
|
||||||
|
|
||||||
|
if word_fits_remaining_space:
|
||||||
|
# Simplest case - the word fits within the remaining width for this line.
|
||||||
|
cell_offset += _cell_len(word)
|
||||||
|
else:
|
||||||
|
# Not enough space remaining for this word on the current line.
|
||||||
|
if word_length > width:
|
||||||
|
# The word doesn't fit on any line, so we can't simply
|
||||||
|
# place it on the next line...
|
||||||
|
if fold:
|
||||||
|
# Fold the word across multiple lines.
|
||||||
|
folded_word = chop_cells(word, width=width)
|
||||||
|
for last, line in loop_last(folded_word):
|
||||||
|
if start:
|
||||||
|
append(start)
|
||||||
|
if last:
|
||||||
|
cell_offset = _cell_len(line)
|
||||||
|
else:
|
||||||
|
start += len(line)
|
||||||
|
else:
|
||||||
|
# Folding isn't allowed, so crop the word.
|
||||||
|
if start:
|
||||||
|
append(start)
|
||||||
|
cell_offset = _cell_len(word)
|
||||||
|
elif cell_offset and start:
|
||||||
|
# The word doesn't fit within the remaining space on the current
|
||||||
|
# line, but it *can* fit on to the next (empty) line.
|
||||||
|
append(start)
|
||||||
|
cell_offset = _cell_len(word)
|
||||||
|
|
||||||
|
return break_positions
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
from .console import Console
|
||||||
|
|
||||||
|
console = Console(width=10)
|
||||||
|
console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345")
|
||||||
|
print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10))
|
||||||
|
|
||||||
|
console = Console(width=20)
|
||||||
|
console.rule()
|
||||||
|
console.print("TextualはPythonの高速アプリケーション開発フレームワークです")
|
||||||
|
|
||||||
|
console.rule()
|
||||||
|
console.print("アプリケーションは1670万色を使用でき")
|
||||||
33
venv/Lib/site-packages/pip/_vendor/rich/abc.py
Normal file
33
venv/Lib/site-packages/pip/_vendor/rich/abc.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
|
||||||
|
class RichRenderable(ABC):
|
||||||
|
"""An abstract base class for Rich renderables.
|
||||||
|
|
||||||
|
Note that there is no need to extend this class, the intended use is to check if an
|
||||||
|
object supports the Rich renderable protocol. For example::
|
||||||
|
|
||||||
|
if isinstance(my_object, RichRenderable):
|
||||||
|
console.print(my_object)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __subclasshook__(cls, other: type) -> bool:
|
||||||
|
"""Check if this class supports the rich render protocol."""
|
||||||
|
return hasattr(other, "__rich_console__") or hasattr(other, "__rich__")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
from pip._vendor.rich.text import Text
|
||||||
|
|
||||||
|
t = Text()
|
||||||
|
print(isinstance(Text, RichRenderable))
|
||||||
|
print(isinstance(t, RichRenderable))
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
pass
|
||||||
|
|
||||||
|
f = Foo()
|
||||||
|
print(isinstance(f, RichRenderable))
|
||||||
|
print(isinstance("", RichRenderable))
|
||||||
306
venv/Lib/site-packages/pip/_vendor/rich/align.py
Normal file
306
venv/Lib/site-packages/pip/_vendor/rich/align.py
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
from itertools import chain
|
||||||
|
from typing import TYPE_CHECKING, Iterable, Optional, Literal
|
||||||
|
|
||||||
|
from .constrain import Constrain
|
||||||
|
from .jupyter import JupyterMixin
|
||||||
|
from .measure import Measurement
|
||||||
|
from .segment import Segment
|
||||||
|
from .style import StyleType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||||
|
|
||||||
|
AlignMethod = Literal["left", "center", "right"]
|
||||||
|
VerticalAlignMethod = Literal["top", "middle", "bottom"]
|
||||||
|
|
||||||
|
|
||||||
|
class Align(JupyterMixin):
|
||||||
|
"""Align a renderable by adding spaces if necessary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
renderable (RenderableType): A console renderable.
|
||||||
|
align (AlignMethod): One of "left", "center", or "right""
|
||||||
|
style (StyleType, optional): An optional style to apply to the background.
|
||||||
|
vertical (Optional[VerticalAlignMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None.
|
||||||
|
pad (bool, optional): Pad the right with spaces. Defaults to True.
|
||||||
|
width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
|
||||||
|
height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: if ``align`` is not one of the expected values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
renderable: "RenderableType",
|
||||||
|
align: AlignMethod = "left",
|
||||||
|
style: Optional[StyleType] = None,
|
||||||
|
*,
|
||||||
|
vertical: Optional[VerticalAlignMethod] = None,
|
||||||
|
pad: bool = True,
|
||||||
|
width: Optional[int] = None,
|
||||||
|
height: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
if align not in ("left", "center", "right"):
|
||||||
|
raise ValueError(
|
||||||
|
f'invalid value for align, expected "left", "center", or "right" (not {align!r})'
|
||||||
|
)
|
||||||
|
if vertical is not None and vertical not in ("top", "middle", "bottom"):
|
||||||
|
raise ValueError(
|
||||||
|
f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})'
|
||||||
|
)
|
||||||
|
self.renderable = renderable
|
||||||
|
self.align = align
|
||||||
|
self.style = style
|
||||||
|
self.vertical = vertical
|
||||||
|
self.pad = pad
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Align({self.renderable!r}, {self.align!r})"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def left(
|
||||||
|
cls,
|
||||||
|
renderable: "RenderableType",
|
||||||
|
style: Optional[StyleType] = None,
|
||||||
|
*,
|
||||||
|
vertical: Optional[VerticalAlignMethod] = None,
|
||||||
|
pad: bool = True,
|
||||||
|
width: Optional[int] = None,
|
||||||
|
height: Optional[int] = None,
|
||||||
|
) -> "Align":
|
||||||
|
"""Align a renderable to the left."""
|
||||||
|
return cls(
|
||||||
|
renderable,
|
||||||
|
"left",
|
||||||
|
style=style,
|
||||||
|
vertical=vertical,
|
||||||
|
pad=pad,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def center(
|
||||||
|
cls,
|
||||||
|
renderable: "RenderableType",
|
||||||
|
style: Optional[StyleType] = None,
|
||||||
|
*,
|
||||||
|
vertical: Optional[VerticalAlignMethod] = None,
|
||||||
|
pad: bool = True,
|
||||||
|
width: Optional[int] = None,
|
||||||
|
height: Optional[int] = None,
|
||||||
|
) -> "Align":
|
||||||
|
"""Align a renderable to the center."""
|
||||||
|
return cls(
|
||||||
|
renderable,
|
||||||
|
"center",
|
||||||
|
style=style,
|
||||||
|
vertical=vertical,
|
||||||
|
pad=pad,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def right(
|
||||||
|
cls,
|
||||||
|
renderable: "RenderableType",
|
||||||
|
style: Optional[StyleType] = None,
|
||||||
|
*,
|
||||||
|
vertical: Optional[VerticalAlignMethod] = None,
|
||||||
|
pad: bool = True,
|
||||||
|
width: Optional[int] = None,
|
||||||
|
height: Optional[int] = None,
|
||||||
|
) -> "Align":
|
||||||
|
"""Align a renderable to the right."""
|
||||||
|
return cls(
|
||||||
|
renderable,
|
||||||
|
"right",
|
||||||
|
style=style,
|
||||||
|
vertical=vertical,
|
||||||
|
pad=pad,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
|
) -> "RenderResult":
|
||||||
|
align = self.align
|
||||||
|
width = console.measure(self.renderable, options=options).maximum
|
||||||
|
rendered = console.render(
|
||||||
|
Constrain(
|
||||||
|
self.renderable, width if self.width is None else min(width, self.width)
|
||||||
|
),
|
||||||
|
options.update(height=None),
|
||||||
|
)
|
||||||
|
lines = list(Segment.split_lines(rendered))
|
||||||
|
width, height = Segment.get_shape(lines)
|
||||||
|
lines = Segment.set_shape(lines, width, height)
|
||||||
|
new_line = Segment.line()
|
||||||
|
excess_space = options.max_width - width
|
||||||
|
style = console.get_style(self.style) if self.style is not None else None
|
||||||
|
|
||||||
|
def generate_segments() -> Iterable[Segment]:
|
||||||
|
if excess_space <= 0:
|
||||||
|
# Exact fit
|
||||||
|
for line in lines:
|
||||||
|
yield from line
|
||||||
|
yield new_line
|
||||||
|
|
||||||
|
elif align == "left":
|
||||||
|
# Pad on the right
|
||||||
|
pad = Segment(" " * excess_space, style) if self.pad else None
|
||||||
|
for line in lines:
|
||||||
|
yield from line
|
||||||
|
if pad:
|
||||||
|
yield pad
|
||||||
|
yield new_line
|
||||||
|
|
||||||
|
elif align == "center":
|
||||||
|
# Pad left and right
|
||||||
|
left = excess_space // 2
|
||||||
|
pad = Segment(" " * left, style)
|
||||||
|
pad_right = (
|
||||||
|
Segment(" " * (excess_space - left), style) if self.pad else None
|
||||||
|
)
|
||||||
|
for line in lines:
|
||||||
|
if left:
|
||||||
|
yield pad
|
||||||
|
yield from line
|
||||||
|
if pad_right:
|
||||||
|
yield pad_right
|
||||||
|
yield new_line
|
||||||
|
|
||||||
|
elif align == "right":
|
||||||
|
# Padding on left
|
||||||
|
pad = Segment(" " * excess_space, style)
|
||||||
|
for line in lines:
|
||||||
|
yield pad
|
||||||
|
yield from line
|
||||||
|
yield new_line
|
||||||
|
|
||||||
|
blank_line = (
|
||||||
|
Segment(f"{' ' * (self.width or options.max_width)}\n", style)
|
||||||
|
if self.pad
|
||||||
|
else Segment("\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
def blank_lines(count: int) -> Iterable[Segment]:
|
||||||
|
if count > 0:
|
||||||
|
for _ in range(count):
|
||||||
|
yield blank_line
|
||||||
|
|
||||||
|
vertical_height = self.height or options.height
|
||||||
|
iter_segments: Iterable[Segment]
|
||||||
|
if self.vertical and vertical_height is not None:
|
||||||
|
if self.vertical == "top":
|
||||||
|
bottom_space = vertical_height - height
|
||||||
|
iter_segments = chain(generate_segments(), blank_lines(bottom_space))
|
||||||
|
elif self.vertical == "middle":
|
||||||
|
top_space = (vertical_height - height) // 2
|
||||||
|
bottom_space = vertical_height - top_space - height
|
||||||
|
iter_segments = chain(
|
||||||
|
blank_lines(top_space),
|
||||||
|
generate_segments(),
|
||||||
|
blank_lines(bottom_space),
|
||||||
|
)
|
||||||
|
else: # self.vertical == "bottom":
|
||||||
|
top_space = vertical_height - height
|
||||||
|
iter_segments = chain(blank_lines(top_space), generate_segments())
|
||||||
|
else:
|
||||||
|
iter_segments = generate_segments()
|
||||||
|
if self.style:
|
||||||
|
style = console.get_style(self.style)
|
||||||
|
iter_segments = Segment.apply_style(iter_segments, style)
|
||||||
|
yield from iter_segments
|
||||||
|
|
||||||
|
def __rich_measure__(
|
||||||
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
|
) -> Measurement:
|
||||||
|
measurement = Measurement.get(console, options, self.renderable)
|
||||||
|
return measurement
|
||||||
|
|
||||||
|
|
||||||
|
class VerticalCenter(JupyterMixin):
|
||||||
|
"""Vertically aligns a renderable.
|
||||||
|
|
||||||
|
Warn:
|
||||||
|
This class is deprecated and may be removed in a future version. Use Align class with
|
||||||
|
`vertical="middle"`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
renderable (RenderableType): A renderable object.
|
||||||
|
style (StyleType, optional): An optional style to apply to the background. Defaults to None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
renderable: "RenderableType",
|
||||||
|
style: Optional[StyleType] = None,
|
||||||
|
) -> None:
|
||||||
|
self.renderable = renderable
|
||||||
|
self.style = style
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"VerticalCenter({self.renderable!r})"
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
|
) -> "RenderResult":
|
||||||
|
style = console.get_style(self.style) if self.style is not None else None
|
||||||
|
lines = console.render_lines(
|
||||||
|
self.renderable, options.update(height=None), pad=False
|
||||||
|
)
|
||||||
|
width, _height = Segment.get_shape(lines)
|
||||||
|
new_line = Segment.line()
|
||||||
|
height = options.height or options.size.height
|
||||||
|
top_space = (height - len(lines)) // 2
|
||||||
|
bottom_space = height - top_space - len(lines)
|
||||||
|
blank_line = Segment(f"{' ' * width}", style)
|
||||||
|
|
||||||
|
def blank_lines(count: int) -> Iterable[Segment]:
|
||||||
|
for _ in range(count):
|
||||||
|
yield blank_line
|
||||||
|
yield new_line
|
||||||
|
|
||||||
|
if top_space > 0:
|
||||||
|
yield from blank_lines(top_space)
|
||||||
|
for line in lines:
|
||||||
|
yield from line
|
||||||
|
yield new_line
|
||||||
|
if bottom_space > 0:
|
||||||
|
yield from blank_lines(bottom_space)
|
||||||
|
|
||||||
|
def __rich_measure__(
|
||||||
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
|
) -> Measurement:
|
||||||
|
measurement = Measurement.get(console, options, self.renderable)
|
||||||
|
return measurement
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
from pip._vendor.rich.console import Console, Group
|
||||||
|
from pip._vendor.rich.highlighter import ReprHighlighter
|
||||||
|
from pip._vendor.rich.panel import Panel
|
||||||
|
|
||||||
|
highlighter = ReprHighlighter()
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
panel = Panel(
|
||||||
|
Group(
|
||||||
|
Align.left(highlighter("align='left'")),
|
||||||
|
Align.center(highlighter("align='center'")),
|
||||||
|
Align.right(highlighter("align='right'")),
|
||||||
|
),
|
||||||
|
width=60,
|
||||||
|
style="on dark_blue",
|
||||||
|
title="Align",
|
||||||
|
)
|
||||||
|
|
||||||
|
console.print(
|
||||||
|
Align.center(panel, vertical="middle", style="on red", height=console.height)
|
||||||
|
)
|
||||||
241
venv/Lib/site-packages/pip/_vendor/rich/ansi.py
Normal file
241
venv/Lib/site-packages/pip/_vendor/rich/ansi.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Iterable, NamedTuple, Optional
|
||||||
|
|
||||||
|
from .color import Color
|
||||||
|
from .style import Style
|
||||||
|
from .text import Text
|
||||||
|
|
||||||
|
re_ansi = re.compile(
|
||||||
|
r"""
|
||||||
|
(?:\x1b[0-?])|
|
||||||
|
(?:\x1b\](.*?)\x1b\\)|
|
||||||
|
(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))
|
||||||
|
""",
|
||||||
|
re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _AnsiToken(NamedTuple):
|
||||||
|
"""Result of ansi tokenized string."""
|
||||||
|
|
||||||
|
plain: str = ""
|
||||||
|
sgr: Optional[str] = ""
|
||||||
|
osc: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
|
def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
|
||||||
|
"""Tokenize a string in to plain text and ANSI codes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ansi_text (str): A String containing ANSI codes.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
AnsiToken: A named tuple of (plain, sgr, osc)
|
||||||
|
"""
|
||||||
|
|
||||||
|
position = 0
|
||||||
|
sgr: Optional[str]
|
||||||
|
osc: Optional[str]
|
||||||
|
for match in re_ansi.finditer(ansi_text):
|
||||||
|
start, end = match.span(0)
|
||||||
|
osc, sgr = match.groups()
|
||||||
|
if start > position:
|
||||||
|
yield _AnsiToken(ansi_text[position:start])
|
||||||
|
if sgr:
|
||||||
|
if sgr == "(":
|
||||||
|
position = end + 1
|
||||||
|
continue
|
||||||
|
if sgr.endswith("m"):
|
||||||
|
yield _AnsiToken("", sgr[1:-1], osc)
|
||||||
|
else:
|
||||||
|
yield _AnsiToken("", sgr, osc)
|
||||||
|
position = end
|
||||||
|
if position < len(ansi_text):
|
||||||
|
yield _AnsiToken(ansi_text[position:])
|
||||||
|
|
||||||
|
|
||||||
|
SGR_STYLE_MAP = {
|
||||||
|
1: "bold",
|
||||||
|
2: "dim",
|
||||||
|
3: "italic",
|
||||||
|
4: "underline",
|
||||||
|
5: "blink",
|
||||||
|
6: "blink2",
|
||||||
|
7: "reverse",
|
||||||
|
8: "conceal",
|
||||||
|
9: "strike",
|
||||||
|
21: "underline2",
|
||||||
|
22: "not dim not bold",
|
||||||
|
23: "not italic",
|
||||||
|
24: "not underline",
|
||||||
|
25: "not blink",
|
||||||
|
26: "not blink2",
|
||||||
|
27: "not reverse",
|
||||||
|
28: "not conceal",
|
||||||
|
29: "not strike",
|
||||||
|
30: "color(0)",
|
||||||
|
31: "color(1)",
|
||||||
|
32: "color(2)",
|
||||||
|
33: "color(3)",
|
||||||
|
34: "color(4)",
|
||||||
|
35: "color(5)",
|
||||||
|
36: "color(6)",
|
||||||
|
37: "color(7)",
|
||||||
|
39: "default",
|
||||||
|
40: "on color(0)",
|
||||||
|
41: "on color(1)",
|
||||||
|
42: "on color(2)",
|
||||||
|
43: "on color(3)",
|
||||||
|
44: "on color(4)",
|
||||||
|
45: "on color(5)",
|
||||||
|
46: "on color(6)",
|
||||||
|
47: "on color(7)",
|
||||||
|
49: "on default",
|
||||||
|
51: "frame",
|
||||||
|
52: "encircle",
|
||||||
|
53: "overline",
|
||||||
|
54: "not frame not encircle",
|
||||||
|
55: "not overline",
|
||||||
|
90: "color(8)",
|
||||||
|
91: "color(9)",
|
||||||
|
92: "color(10)",
|
||||||
|
93: "color(11)",
|
||||||
|
94: "color(12)",
|
||||||
|
95: "color(13)",
|
||||||
|
96: "color(14)",
|
||||||
|
97: "color(15)",
|
||||||
|
100: "on color(8)",
|
||||||
|
101: "on color(9)",
|
||||||
|
102: "on color(10)",
|
||||||
|
103: "on color(11)",
|
||||||
|
104: "on color(12)",
|
||||||
|
105: "on color(13)",
|
||||||
|
106: "on color(14)",
|
||||||
|
107: "on color(15)",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AnsiDecoder:
|
||||||
|
"""Translate ANSI code in to styled Text."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.style = Style.null()
|
||||||
|
|
||||||
|
def decode(self, terminal_text: str) -> Iterable[Text]:
|
||||||
|
"""Decode ANSI codes in an iterable of lines.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lines (Iterable[str]): An iterable of lines of terminal output.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Text: Marked up Text.
|
||||||
|
"""
|
||||||
|
for line in terminal_text.splitlines():
|
||||||
|
yield self.decode_line(line)
|
||||||
|
|
||||||
|
def decode_line(self, line: str) -> Text:
|
||||||
|
"""Decode a line containing ansi codes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
line (str): A line of terminal output.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: A Text instance marked up according to ansi codes.
|
||||||
|
"""
|
||||||
|
from_ansi = Color.from_ansi
|
||||||
|
from_rgb = Color.from_rgb
|
||||||
|
_Style = Style
|
||||||
|
text = Text()
|
||||||
|
append = text.append
|
||||||
|
line = line.rsplit("\r", 1)[-1]
|
||||||
|
for plain_text, sgr, osc in _ansi_tokenize(line):
|
||||||
|
if plain_text:
|
||||||
|
append(plain_text, self.style or None)
|
||||||
|
elif osc is not None:
|
||||||
|
if osc.startswith("8;"):
|
||||||
|
_params, semicolon, link = osc[2:].partition(";")
|
||||||
|
if semicolon:
|
||||||
|
self.style = self.style.update_link(link or None)
|
||||||
|
elif sgr is not None:
|
||||||
|
# Translate in to semi-colon separated codes
|
||||||
|
# Ignore invalid codes, because we want to be lenient
|
||||||
|
codes = [
|
||||||
|
min(255, int(_code) if _code else 0)
|
||||||
|
for _code in sgr.split(";")
|
||||||
|
if _code.isdigit() or _code == ""
|
||||||
|
]
|
||||||
|
iter_codes = iter(codes)
|
||||||
|
for code in iter_codes:
|
||||||
|
if code == 0:
|
||||||
|
# reset
|
||||||
|
self.style = _Style.null()
|
||||||
|
elif code in SGR_STYLE_MAP:
|
||||||
|
# styles
|
||||||
|
self.style += _Style.parse(SGR_STYLE_MAP[code])
|
||||||
|
elif code == 38:
|
||||||
|
# Foreground
|
||||||
|
with suppress(StopIteration):
|
||||||
|
color_type = next(iter_codes)
|
||||||
|
if color_type == 5:
|
||||||
|
self.style += _Style.from_color(
|
||||||
|
from_ansi(next(iter_codes))
|
||||||
|
)
|
||||||
|
elif color_type == 2:
|
||||||
|
self.style += _Style.from_color(
|
||||||
|
from_rgb(
|
||||||
|
next(iter_codes),
|
||||||
|
next(iter_codes),
|
||||||
|
next(iter_codes),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif code == 48:
|
||||||
|
# Background
|
||||||
|
with suppress(StopIteration):
|
||||||
|
color_type = next(iter_codes)
|
||||||
|
if color_type == 5:
|
||||||
|
self.style += _Style.from_color(
|
||||||
|
None, from_ansi(next(iter_codes))
|
||||||
|
)
|
||||||
|
elif color_type == 2:
|
||||||
|
self.style += _Style.from_color(
|
||||||
|
None,
|
||||||
|
from_rgb(
|
||||||
|
next(iter_codes),
|
||||||
|
next(iter_codes),
|
||||||
|
next(iter_codes),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import pty
|
||||||
|
import sys
|
||||||
|
|
||||||
|
decoder = AnsiDecoder()
|
||||||
|
|
||||||
|
stdout = io.BytesIO()
|
||||||
|
|
||||||
|
def read(fd: int) -> bytes:
|
||||||
|
data = os.read(fd, 1024)
|
||||||
|
stdout.write(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
pty.spawn(sys.argv[1:], read)
|
||||||
|
|
||||||
|
from .console import Console
|
||||||
|
|
||||||
|
console = Console(record=True)
|
||||||
|
|
||||||
|
stdout_result = stdout.getvalue().decode("utf-8")
|
||||||
|
print(stdout_result)
|
||||||
|
|
||||||
|
for line in decoder.decode(stdout_result):
|
||||||
|
console.print(line)
|
||||||
|
|
||||||
|
console.save_html("stdout.html")
|
||||||
Reference in New Issue
Block a user