Загрузить файлы в «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