File manager - Edit - /home/newsbmcs.com/public_html/static/img/logo/pip.tar
Back
_vendor/rich/_palettes.py 0000644 00000015627 15030420735 0011471 0 ustar 00 from .palette import Palette # Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column) WINDOWS_PALETTE = Palette( [ (12, 12, 12), (197, 15, 31), (19, 161, 14), (193, 156, 0), (0, 55, 218), (136, 23, 152), (58, 150, 221), (204, 204, 204), (118, 118, 118), (231, 72, 86), (22, 198, 12), (249, 241, 165), (59, 120, 255), (180, 0, 158), (97, 214, 214), (242, 242, 242), ] ) # # The standard ansi colors (including bright variants) STANDARD_PALETTE = Palette( [ (0, 0, 0), (170, 0, 0), (0, 170, 0), (170, 85, 0), (0, 0, 170), (170, 0, 170), (0, 170, 170), (170, 170, 170), (85, 85, 85), (255, 85, 85), (85, 255, 85), (255, 255, 85), (85, 85, 255), (255, 85, 255), (85, 255, 255), (255, 255, 255), ] ) # The 256 color palette EIGHT_BIT_PALETTE = Palette( [ (0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0), (0, 0, 128), (128, 0, 128), (0, 128, 128), (192, 192, 192), (128, 128, 128), (255, 0, 0), (0, 255, 0), (255, 255, 0), (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255), (0, 0, 0), (0, 0, 95), (0, 0, 135), (0, 0, 175), (0, 0, 215), (0, 0, 255), (0, 95, 0), (0, 95, 95), (0, 95, 135), (0, 95, 175), (0, 95, 215), (0, 95, 255), (0, 135, 0), (0, 135, 95), (0, 135, 135), (0, 135, 175), (0, 135, 215), (0, 135, 255), (0, 175, 0), (0, 175, 95), (0, 175, 135), (0, 175, 175), (0, 175, 215), (0, 175, 255), (0, 215, 0), (0, 215, 95), (0, 215, 135), (0, 215, 175), (0, 215, 215), (0, 215, 255), (0, 255, 0), (0, 255, 95), (0, 255, 135), (0, 255, 175), (0, 255, 215), (0, 255, 255), (95, 0, 0), (95, 0, 95), (95, 0, 135), (95, 0, 175), (95, 0, 215), (95, 0, 255), (95, 95, 0), (95, 95, 95), (95, 95, 135), (95, 95, 175), (95, 95, 215), (95, 95, 255), (95, 135, 0), (95, 135, 95), (95, 135, 135), (95, 135, 175), (95, 135, 215), (95, 135, 255), (95, 175, 0), (95, 175, 95), (95, 175, 135), (95, 175, 175), (95, 175, 215), (95, 175, 255), (95, 215, 0), (95, 215, 95), (95, 215, 135), (95, 215, 175), (95, 215, 215), (95, 215, 255), (95, 255, 0), (95, 255, 95), (95, 255, 135), (95, 255, 175), (95, 255, 215), (95, 255, 255), (135, 0, 0), (135, 0, 95), (135, 0, 135), (135, 0, 175), (135, 0, 215), (135, 0, 255), (135, 95, 0), (135, 95, 95), (135, 95, 135), (135, 95, 175), (135, 95, 215), (135, 95, 255), (135, 135, 0), (135, 135, 95), (135, 135, 135), (135, 135, 175), (135, 135, 215), (135, 135, 255), (135, 175, 0), (135, 175, 95), (135, 175, 135), (135, 175, 175), (135, 175, 215), (135, 175, 255), (135, 215, 0), (135, 215, 95), (135, 215, 135), (135, 215, 175), (135, 215, 215), (135, 215, 255), (135, 255, 0), (135, 255, 95), (135, 255, 135), (135, 255, 175), (135, 255, 215), (135, 255, 255), (175, 0, 0), (175, 0, 95), (175, 0, 135), (175, 0, 175), (175, 0, 215), (175, 0, 255), (175, 95, 0), (175, 95, 95), (175, 95, 135), (175, 95, 175), (175, 95, 215), (175, 95, 255), (175, 135, 0), (175, 135, 95), (175, 135, 135), (175, 135, 175), (175, 135, 215), (175, 135, 255), (175, 175, 0), (175, 175, 95), (175, 175, 135), (175, 175, 175), (175, 175, 215), (175, 175, 255), (175, 215, 0), (175, 215, 95), (175, 215, 135), (175, 215, 175), (175, 215, 215), (175, 215, 255), (175, 255, 0), (175, 255, 95), (175, 255, 135), (175, 255, 175), (175, 255, 215), (175, 255, 255), (215, 0, 0), (215, 0, 95), (215, 0, 135), (215, 0, 175), (215, 0, 215), (215, 0, 255), (215, 95, 0), (215, 95, 95), (215, 95, 135), (215, 95, 175), (215, 95, 215), (215, 95, 255), (215, 135, 0), (215, 135, 95), (215, 135, 135), (215, 135, 175), (215, 135, 215), (215, 135, 255), (215, 175, 0), (215, 175, 95), (215, 175, 135), (215, 175, 175), (215, 175, 215), (215, 175, 255), (215, 215, 0), (215, 215, 95), (215, 215, 135), (215, 215, 175), (215, 215, 215), (215, 215, 255), (215, 255, 0), (215, 255, 95), (215, 255, 135), (215, 255, 175), (215, 255, 215), (215, 255, 255), (255, 0, 0), (255, 0, 95), (255, 0, 135), (255, 0, 175), (255, 0, 215), (255, 0, 255), (255, 95, 0), (255, 95, 95), (255, 95, 135), (255, 95, 175), (255, 95, 215), (255, 95, 255), (255, 135, 0), (255, 135, 95), (255, 135, 135), (255, 135, 175), (255, 135, 215), (255, 135, 255), (255, 175, 0), (255, 175, 95), (255, 175, 135), (255, 175, 175), (255, 175, 215), (255, 175, 255), (255, 215, 0), (255, 215, 95), (255, 215, 135), (255, 215, 175), (255, 215, 215), (255, 215, 255), (255, 255, 0), (255, 255, 95), (255, 255, 135), (255, 255, 175), (255, 255, 215), (255, 255, 255), (8, 8, 8), (18, 18, 18), (28, 28, 28), (38, 38, 38), (48, 48, 48), (58, 58, 58), (68, 68, 68), (78, 78, 78), (88, 88, 88), (98, 98, 98), (108, 108, 108), (118, 118, 118), (128, 128, 128), (138, 138, 138), (148, 148, 148), (158, 158, 158), (168, 168, 168), (178, 178, 178), (188, 188, 188), (198, 198, 198), (208, 208, 208), (218, 218, 218), (228, 228, 228), (238, 238, 238), ] ) _vendor/rich/cells.py 0000644 00000010275 15030420735 0010605 0 ustar 00 from functools import lru_cache import re from typing import Dict, List from ._cell_widths import CELL_WIDTHS from ._lru_cache import LRUCache # Regex to match sequence of the most common character ranges _is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match def cell_len(text: str, _cache: Dict[str, int] = LRUCache(1024 * 4)) -> int: """Get the number of cells required to display text. 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) else: cached_result = _cache.get(text, None) if cached_result is not None: return cached_result _get_size = get_character_cell_size total_size = sum(_get_size(character) for character in text) if len(text) <= 64: _cache[text] = total_size return total_size @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. """ if _is_single_cell_widths(character): return 1 return _get_codepoint_cell_size(ord(character)) @lru_cache(maxsize=4096) def _get_codepoint_cell_size(codepoint: int) -> 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. """ _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 not total: 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 # TODO: This is inefficient # TODO: This might not work with CWJ type characters def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]: """Break text in to equal (cell) length strings.""" _get_character_cell_size = get_character_cell_size characters = [ (character, _get_character_cell_size(character)) for character in text ][::-1] total_size = position lines: List[List[str]] = [[]] append = lines[-1].append pop = characters.pop while characters: character, size = pop() if total_size + size > max_size: lines.append([character]) append = lines[-1].append total_size = size else: total_size += size append(character) 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) _vendor/rich/rule.py 0000644 00000010145 15030420735 0010446 0 ustar 00 from typing import Union from .align import AlignMethod from .cells import cell_len, set_cell_size from .console import Console, ConsoleOptions, RenderResult from .jupyter import JupyterMixin from .style import Style from .text import Text class Rule(JupyterMixin): """A console renderable to draw a horizontal rule (line). Args: title (Union[str, Text], optional): Text to render in the rule. Defaults to "". characters (str, optional): Character(s) used to draw the line. Defaults to "─". style (StyleType, optional): Style of Rule. Defaults to "rule.line". end (str, optional): Character at end of Rule. defaults to "\\\\n" align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". """ def __init__( self, title: Union[str, Text] = "", *, characters: str = "─", style: Union[str, Style] = "rule.line", end: str = "\n", align: AlignMethod = "center", ) -> None: if cell_len(characters) < 1: raise ValueError( "'characters' argument must have a cell width of at least 1" ) if align not in ("left", "center", "right"): raise ValueError( f'invalid value for align, expected "left", "center", "right" (not {align!r})' ) self.title = title self.characters = characters self.style = style self.end = end self.align = align def __repr__(self) -> str: return f"Rule({self.title!r}, {self.characters!r})" def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: width = options.max_width # Python3.6 doesn't have an isascii method on str isascii = getattr(str, "isascii", None) or ( lambda s: all(ord(c) < 128 for c in s) ) characters = ( "-" if (options.ascii_only and not isascii(self.characters)) else self.characters ) chars_len = cell_len(characters) if not self.title: rule_text = Text(characters * ((width // chars_len) + 1), self.style) rule_text.truncate(width) rule_text.plain = set_cell_size(rule_text.plain, width) yield rule_text return if isinstance(self.title, Text): title_text = self.title else: title_text = console.render_str(self.title, style="rule.text") title_text.plain = title_text.plain.replace("\n", " ") title_text.expand_tabs() rule_text = Text(end=self.end) if self.align == "center": title_text.truncate(width - 4, overflow="ellipsis") side_width = (width - cell_len(title_text.plain)) // 2 left = Text(characters * (side_width // chars_len + 1)) left.truncate(side_width - 1) right_length = width - cell_len(left.plain) - cell_len(title_text.plain) right = Text(characters * (side_width // chars_len + 1)) right.truncate(right_length) rule_text.append(left.plain + " ", self.style) rule_text.append(title_text) rule_text.append(" " + right.plain, self.style) elif self.align == "left": title_text.truncate(width - 2, overflow="ellipsis") rule_text.append(title_text) rule_text.append(" ") rule_text.append(characters * (width - rule_text.cell_len), self.style) elif self.align == "right": title_text.truncate(width - 2, overflow="ellipsis") rule_text.append(characters * (width - title_text.cell_len - 1), self.style) rule_text.append(" ") rule_text.append(title_text) rule_text.plain = set_cell_size(rule_text.plain, width) yield rule_text if __name__ == "__main__": # pragma: no cover from pip._vendor.rich.console import Console import sys try: text = sys.argv[1] except IndexError: text = "Hello, World" console = Console() console.print(Rule(title=text)) _vendor/rich/box.py 0000644 00000021555 15030420735 0010276 0 ustar 00 import sys from typing import TYPE_CHECKING, Iterable, List if sys.version_info >= (3, 8): from typing import Literal else: from pip._vendor.typing_extensions import Literal # pragma: no cover 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_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) ASCII: Box = Box( """\ +--+ | || |-+| | || |-+| |-+| | || +--+ """, ascii=True, ) ASCII2: Box = Box( """\ +-++ | || +-++ | || +-++ +-++ | || +-++ """, ascii=True, ) ASCII_DOUBLE_HEAD: Box = Box( """\ +-++ | || +=++ | || +-++ +-++ | || +-++ """, ascii=True, ) SQUARE: Box = Box( """\ ┌─┬┐ │ ││ ├─┼┤ │ ││ ├─┼┤ ├─┼┤ │ ││ └─┴┘ """ ) SQUARE_DOUBLE_HEAD: Box = Box( """\ ┌─┬┐ │ ││ ╞═╪╡ │ ││ ├─┼┤ ├─┼┤ │ ││ └─┴┘ """ ) MINIMAL: Box = Box( """\ ╷ │ ╶─┼╴ │ ╶─┼╴ ╶─┼╴ │ ╵ """ ) MINIMAL_HEAVY_HEAD: Box = Box( """\ ╷ │ ╺━┿╸ │ ╶─┼╴ ╶─┼╴ │ ╵ """ ) MINIMAL_DOUBLE_HEAD: Box = Box( """\ ╷ │ ═╪ │ ─┼ ─┼ │ ╵ """ ) SIMPLE: Box = Box( """\ ── ── """ ) SIMPLE_HEAD: Box = Box( """\ ── """ ) SIMPLE_HEAVY: Box = Box( """\ ━━ ━━ """ ) HORIZONTALS: Box = Box( """\ ── ── ── ── ── """ ) ROUNDED: Box = Box( """\ ╭─┬╮ │ ││ ├─┼┤ │ ││ ├─┼┤ ├─┼┤ │ ││ ╰─┴╯ """ ) HEAVY: Box = Box( """\ ┏━┳┓ ┃ ┃┃ ┣━╋┫ ┃ ┃┃ ┣━╋┫ ┣━╋┫ ┃ ┃┃ ┗━┻┛ """ ) HEAVY_EDGE: Box = Box( """\ ┏━┯┓ ┃ │┃ ┠─┼┨ ┃ │┃ ┠─┼┨ ┠─┼┨ ┃ │┃ ┗━┷┛ """ ) HEAVY_HEAD: Box = Box( """\ ┏━┳┓ ┃ ┃┃ ┡━╇┩ │ ││ ├─┼┤ ├─┼┤ │ ││ └─┴┘ """ ) DOUBLE: Box = Box( """\ ╔═╦╗ ║ ║║ ╠═╬╣ ║ ║║ ╠═╬╣ ╠═╬╣ ║ ║║ ╚═╩╝ """ ) DOUBLE_EDGE: Box = Box( """\ ╔═╤╗ ║ │║ ╟─┼╢ ║ │║ ╟─┼╢ ╟─┼╢ ║ │║ ╚═╧╝ """ ) # 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, } 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", ] 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_html("box.html", inline_styles=True) _vendor/rich/diagnose.py 0000644 00000000267 15030420735 0011274 0 ustar 00 if __name__ == "__main__": # pragma: no cover from pip._vendor.rich.console import Console from pip._vendor.rich import inspect console = Console() inspect(console) _vendor/rich/text.py 0000644 00000126610 15030420735 0010470 0 ustar 00 import re from functools import partial, reduce from math import gcd from operator import itemgetter from pip._vendor.rich.emoji import EmojiVariant from typing import ( TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union, ) from ._loop import loop_last from ._pick import pick_bool from ._wrap import divide_line from .align import AlignMethod from .cells import cell_len, set_cell_size from .containers import Lines from .control import strip_control_codes from .emoji import EmojiVariant from .jupyter import JupyterMixin from .measure import Measurement from .segment import Segment from .style import Style, StyleType if TYPE_CHECKING: # pragma: no cover from .console import Console, ConsoleOptions, JustifyMethod, OverflowMethod DEFAULT_JUSTIFY: "JustifyMethod" = "default" DEFAULT_OVERFLOW: "OverflowMethod" = "fold" _re_whitespace = re.compile(r"\s+$") TextType = Union[str, "Text"] GetStyleCallable = Callable[[str], Optional[StyleType]] class Span(NamedTuple): """A marked up region in some text.""" start: int """Span start index.""" end: int """Span end index.""" style: Union[str, Style] """Style associated with the span.""" def __repr__(self) -> str: return ( f"Span({self.start}, {self.end}, {self.style!r})" if (isinstance(self.style, Style) and self.style._meta) else f"Span({self.start}, {self.end}, {repr(self.style)})" ) def __bool__(self) -> bool: return self.end > self.start def split(self, offset: int) -> Tuple["Span", Optional["Span"]]: """Split a span in to 2 from a given offset.""" if offset < self.start: return self, None if offset >= self.end: return self, None start, end, style = self span1 = Span(start, min(end, offset), style) span2 = Span(span1.end, end, style) return span1, span2 def move(self, offset: int) -> "Span": """Move start and end by a given offset. Args: offset (int): Number of characters to add to start and end. Returns: TextSpan: A new TextSpan with adjusted position. """ start, end, style = self return Span(start + offset, end + offset, style) def right_crop(self, offset: int) -> "Span": """Crop the span at the given offset. Args: offset (int): A value between start and end. Returns: Span: A new (possibly smaller) span. """ start, end, style = self if offset >= end: return self return Span(start, min(offset, end), style) class Text(JupyterMixin): """Text with color / style. Args: text (str, optional): Default unstyled text. Defaults to "". style (Union[str, Style], optional): Base style for text. Defaults to "". justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. end (str, optional): Character to end text with. Defaults to "\\\\n". tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. spans (List[Span], optional). A list of predefined style spans. Defaults to None. """ __slots__ = [ "_text", "style", "justify", "overflow", "no_wrap", "end", "tab_size", "_spans", "_length", ] def __init__( self, text: str = "", style: Union[str, Style] = "", *, justify: Optional["JustifyMethod"] = None, overflow: Optional["OverflowMethod"] = None, no_wrap: Optional[bool] = None, end: str = "\n", tab_size: Optional[int] = 8, spans: Optional[List[Span]] = None, ) -> None: self._text = [strip_control_codes(text)] self.style = style self.justify: Optional["JustifyMethod"] = justify self.overflow: Optional["OverflowMethod"] = overflow self.no_wrap = no_wrap self.end = end self.tab_size = tab_size self._spans: List[Span] = spans or [] self._length: int = len(text) def __len__(self) -> int: return self._length def __bool__(self) -> bool: return bool(self._length) def __str__(self) -> str: return self.plain def __repr__(self) -> str: return f"<text {self.plain!r} {self._spans!r}>" def __add__(self, other: Any) -> "Text": if isinstance(other, (str, Text)): result = self.copy() result.append(other) return result return NotImplemented def __eq__(self, other: object) -> bool: if not isinstance(other, Text): return NotImplemented return self.plain == other.plain and self._spans == other._spans def __contains__(self, other: object) -> bool: if isinstance(other, str): return other in self.plain elif isinstance(other, Text): return other.plain in self.plain return False def __getitem__(self, slice: Union[int, slice]) -> "Text": def get_text_at(offset: int) -> "Text": _Span = Span text = Text( self.plain[offset], spans=[ _Span(0, 1, style) for start, end, style in self._spans if end > offset >= start ], end="", ) return text if isinstance(slice, int): return get_text_at(slice) else: start, stop, step = slice.indices(len(self.plain)) if step == 1: lines = self.divide([start, stop]) return lines[1] else: # This would be a bit of work to implement efficiently # For now, its not required raise TypeError("slices with step!=1 are not supported") @property def cell_len(self) -> int: """Get the number of cells required to render this text.""" return cell_len(self.plain) @property def markup(self) -> str: """Get console markup to render this Text. Returns: str: A string potentially creating markup tags. """ from .markup import escape output: List[str] = [] plain = self.plain markup_spans = [ (0, False, self.style), *((span.start, False, span.style) for span in self._spans), *((span.end, True, span.style) for span in self._spans), (len(plain), True, self.style), ] markup_spans.sort(key=itemgetter(0, 1)) position = 0 append = output.append for offset, closing, style in markup_spans: if offset > position: append(escape(plain[position:offset])) position = offset if style: append(f"[/{style}]" if closing else f"[{style}]") markup = "".join(output) return markup @classmethod def from_markup( cls, text: str, *, style: Union[str, Style] = "", emoji: bool = True, emoji_variant: Optional[EmojiVariant] = None, justify: Optional["JustifyMethod"] = None, overflow: Optional["OverflowMethod"] = None, ) -> "Text": """Create Text instance from markup. Args: text (str): A string containing console markup. emoji (bool, optional): Also render emoji code. Defaults to True. justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. Returns: Text: A Text instance with markup rendered. """ from .markup import render rendered_text = render(text, style, emoji=emoji, emoji_variant=emoji_variant) rendered_text.justify = justify rendered_text.overflow = overflow return rendered_text @classmethod def from_ansi( cls, text: str, *, style: Union[str, Style] = "", justify: Optional["JustifyMethod"] = None, overflow: Optional["OverflowMethod"] = None, no_wrap: Optional[bool] = None, end: str = "\n", tab_size: Optional[int] = 8, ) -> "Text": """Create a Text object from a string containing ANSI escape codes. Args: text (str): A string containing escape codes. style (Union[str, Style], optional): Base style for text. Defaults to "". justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. no_wrap (bool, optional): Disable text wrapping, or None for default. Defaults to None. end (str, optional): Character to end text with. Defaults to "\\\\n". tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. """ from .ansi import AnsiDecoder joiner = Text( "\n", justify=justify, overflow=overflow, no_wrap=no_wrap, end=end, tab_size=tab_size, style=style, ) decoder = AnsiDecoder() result = joiner.join(line for line in decoder.decode(text)) return result @classmethod def styled( cls, text: str, style: StyleType = "", *, justify: Optional["JustifyMethod"] = None, overflow: Optional["OverflowMethod"] = None, ) -> "Text": """Construct a Text instance with a pre-applied styled. A style applied in this way won't be used to pad the text when it is justified. Args: text (str): A string containing console markup. style (Union[str, Style]): Style to apply to the text. Defaults to "". justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. Returns: Text: A text instance with a style applied to the entire string. """ styled_text = cls(text, justify=justify, overflow=overflow) styled_text.stylize(style) return styled_text @classmethod def assemble( cls, *parts: Union[str, "Text", Tuple[str, StyleType]], style: Union[str, Style] = "", justify: Optional["JustifyMethod"] = None, overflow: Optional["OverflowMethod"] = None, no_wrap: Optional[bool] = None, end: str = "\n", tab_size: int = 8, meta: Optional[Dict[str, Any]] = None, ) -> "Text": """Construct a text instance by combining a sequence of strings with optional styles. The positional arguments should be either strings, or a tuple of string + style. Args: style (Union[str, Style], optional): Base style for text. Defaults to "". justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None. overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None. end (str, optional): Character to end text with. Defaults to "\\\\n". tab_size (int): Number of spaces per tab, or ``None`` to use ``console.tab_size``. Defaults to 8. meta (Dict[str, Any], optional). Meta data to apply to text, or None for no meta data. Default to None Returns: Text: A new text instance. """ text = cls( style=style, justify=justify, overflow=overflow, no_wrap=no_wrap, end=end, tab_size=tab_size, ) append = text.append _Text = Text for part in parts: if isinstance(part, (_Text, str)): append(part) else: append(*part) if meta: text.apply_meta(meta) return text @property def plain(self) -> str: """Get the text as a single string.""" if len(self._text) != 1: self._text[:] = ["".join(self._text)] return self._text[0] @plain.setter def plain(self, new_text: str) -> None: """Set the text to a new value.""" if new_text != self.plain: self._text[:] = [new_text] old_length = self._length self._length = len(new_text) if old_length > self._length: self._trim_spans() @property def spans(self) -> List[Span]: """Get a reference to the internal list of spans.""" return self._spans @spans.setter def spans(self, spans: List[Span]) -> None: """Set spans.""" self._spans = spans[:] def blank_copy(self, plain: str = "") -> "Text": """Return a new Text instance with copied meta data (but not the string or spans).""" copy_self = Text( plain, style=self.style, justify=self.justify, overflow=self.overflow, no_wrap=self.no_wrap, end=self.end, tab_size=self.tab_size, ) return copy_self def copy(self) -> "Text": """Return a copy of this instance.""" copy_self = Text( self.plain, style=self.style, justify=self.justify, overflow=self.overflow, no_wrap=self.no_wrap, end=self.end, tab_size=self.tab_size, ) copy_self._spans[:] = self._spans return copy_self def stylize( self, style: Union[str, Style], start: int = 0, end: Optional[int] = None, ) -> None: """Apply a style to the text, or a portion of the text. Args: style (Union[str, Style]): Style instance or style definition to apply. start (int): Start offset (negative indexing is supported). Defaults to 0. end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. """ if style: length = len(self) if start < 0: start = length + start if end is None: end = length if end < 0: end = length + end if start >= length or end <= start: # Span not in text or not valid return self._spans.append(Span(start, min(length, end), style)) def apply_meta( self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None ) -> None: """Apply meta data to the text, or a portion of the text. Args: meta (Dict[str, Any]): A dict of meta information. start (int): Start offset (negative indexing is supported). Defaults to 0. end (Optional[int], optional): End offset (negative indexing is supported), or None for end of text. Defaults to None. """ style = Style.from_meta(meta) self.stylize(style, start=start, end=end) def on(self, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Text": """Apply event handlers (used by Textual project). Example: >>> from rich.text import Text >>> text = Text("hello world") >>> text.on(click="view.toggle('world')") Args: meta (Dict[str, Any]): Mapping of meta information. **handlers: Keyword args are prefixed with "@" to defined handlers. Returns: Text: Self is returned to method may be chained. """ meta = {} if meta is None else meta meta.update({f"@{key}": value for key, value in handlers.items()}) self.stylize(Style.from_meta(meta)) return self def remove_suffix(self, suffix: str) -> None: """Remove a suffix if it exists. Args: suffix (str): Suffix to remove. """ if self.plain.endswith(suffix): self.right_crop(len(suffix)) def get_style_at_offset(self, console: "Console", offset: int) -> Style: """Get the style of a character at give offset. Args: console (~Console): Console where text will be rendered. offset (int): Offset in to text (negative indexing supported) Returns: Style: A Style instance. """ # TODO: This is a little inefficient, it is only used by full justify if offset < 0: offset = len(self) + offset get_style = console.get_style style = get_style(self.style).copy() for start, end, span_style in self._spans: if end > offset >= start: style += get_style(span_style, default="") return style def highlight_regex( self, re_highlight: str, style: Optional[Union[GetStyleCallable, StyleType]] = None, *, style_prefix: str = "", ) -> int: """Highlight text with a regular expression, where group names are translated to styles. Args: re_highlight (str): A regular expression. style (Union[GetStyleCallable, StyleType]): Optional style to apply to whole match, or a callable which accepts the matched text and returns a style. Defaults to None. style_prefix (str, optional): Optional prefix to add to style group names. Returns: int: Number of regex matches """ count = 0 append_span = self._spans.append _Span = Span plain = self.plain for match in re.finditer(re_highlight, plain): get_span = match.span if style: start, end = get_span() match_style = style(plain[start:end]) if callable(style) else style if match_style is not None and end > start: append_span(_Span(start, end, match_style)) count += 1 for name in match.groupdict().keys(): start, end = get_span(name) if start != -1 and end > start: append_span(_Span(start, end, f"{style_prefix}{name}")) return count def highlight_words( self, words: Iterable[str], style: Union[str, Style], *, case_sensitive: bool = True, ) -> int: """Highlight words with a style. Args: words (Iterable[str]): Worlds to highlight. style (Union[str, Style]): Style to apply. case_sensitive (bool, optional): Enable case sensitive matchings. Defaults to True. Returns: int: Number of words highlighted. """ re_words = "|".join(re.escape(word) for word in words) add_span = self._spans.append count = 0 _Span = Span for match in re.finditer( re_words, self.plain, flags=0 if case_sensitive else re.IGNORECASE ): start, end = match.span(0) add_span(_Span(start, end, style)) count += 1 return count def rstrip(self) -> None: """Strip whitespace from end of text.""" self.plain = self.plain.rstrip() def rstrip_end(self, size: int) -> None: """Remove whitespace beyond a certain width at the end of the text. Args: size (int): The desired size of the text. """ text_length = len(self) if text_length > size: excess = text_length - size whitespace_match = _re_whitespace.search(self.plain) if whitespace_match is not None: whitespace_count = len(whitespace_match.group(0)) self.right_crop(min(whitespace_count, excess)) def set_length(self, new_length: int) -> None: """Set new length of the text, clipping or padding is required.""" length = len(self) if length != new_length: if length < new_length: self.pad_right(new_length - length) else: self.right_crop(length - new_length) def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> Iterable[Segment]: tab_size: int = console.tab_size or self.tab_size or 8 justify = self.justify or options.justify or DEFAULT_JUSTIFY overflow = self.overflow or options.overflow or DEFAULT_OVERFLOW lines = self.wrap( console, options.max_width, justify=justify, overflow=overflow, tab_size=tab_size or 8, no_wrap=pick_bool(self.no_wrap, options.no_wrap, False), ) all_lines = Text("\n").join(lines) yield from all_lines.render(console, end=self.end) def __rich_measure__( self, console: "Console", options: "ConsoleOptions" ) -> Measurement: text = self.plain lines = text.splitlines() max_text_width = max(cell_len(line) for line in lines) if lines else 0 words = text.split() min_text_width = ( max(cell_len(word) for word in words) if words else max_text_width ) return Measurement(min_text_width, max_text_width) def render(self, console: "Console", end: str = "") -> Iterable["Segment"]: """Render the text as Segments. Args: console (Console): Console instance. end (Optional[str], optional): Optional end character. Returns: Iterable[Segment]: Result of render that may be written to the console. """ _Segment = Segment text = self.plain if not self._spans: yield Segment(text) if end: yield _Segment(end) return get_style = partial(console.get_style, default=Style.null()) enumerated_spans = list(enumerate(self._spans, 1)) style_map = {index: get_style(span.style) for index, span in enumerated_spans} style_map[0] = get_style(self.style) spans = [ (0, False, 0), *((span.start, False, index) for index, span in enumerated_spans), *((span.end, True, index) for index, span in enumerated_spans), (len(text), True, 0), ] spans.sort(key=itemgetter(0, 1)) stack: List[int] = [] stack_append = stack.append stack_pop = stack.remove style_cache: Dict[Tuple[Style, ...], Style] = {} style_cache_get = style_cache.get combine = Style.combine def get_current_style() -> Style: """Construct current style from stack.""" styles = tuple(style_map[_style_id] for _style_id in sorted(stack)) cached_style = style_cache_get(styles) if cached_style is not None: return cached_style current_style = combine(styles) style_cache[styles] = current_style return current_style for (offset, leaving, style_id), (next_offset, _, _) in zip(spans, spans[1:]): if leaving: stack_pop(style_id) else: stack_append(style_id) if next_offset > offset: yield _Segment(text[offset:next_offset], get_current_style()) if end: yield _Segment(end) def join(self, lines: Iterable["Text"]) -> "Text": """Join text together with this instance as the separator. Args: lines (Iterable[Text]): An iterable of Text instances to join. Returns: Text: A new text instance containing join text. """ new_text = self.blank_copy() def iter_text() -> Iterable["Text"]: if self.plain: for last, line in loop_last(lines): yield line if not last: yield self else: yield from lines extend_text = new_text._text.extend append_span = new_text._spans.append extend_spans = new_text._spans.extend offset = 0 _Span = Span for text in iter_text(): extend_text(text._text) if text.style: append_span(_Span(offset, offset + len(text), text.style)) extend_spans( _Span(offset + start, offset + end, style) for start, end, style in text._spans ) offset += len(text) new_text._length = offset return new_text def expand_tabs(self, tab_size: Optional[int] = None) -> None: """Converts tabs to spaces. Args: tab_size (int, optional): Size of tabs. Defaults to 8. """ if "\t" not in self.plain: return pos = 0 if tab_size is None: tab_size = self.tab_size assert tab_size is not None result = self.blank_copy() append = result.append _style = self.style for line in self.split("\n", include_separator=True): parts = line.split("\t", include_separator=True) for part in parts: if part.plain.endswith("\t"): part._text = [part.plain[:-1] + " "] append(part) pos += len(part) spaces = tab_size - ((pos - 1) % tab_size) - 1 if spaces: append(" " * spaces, _style) pos += spaces else: append(part) self._text = [result.plain] self._length = len(self.plain) self._spans[:] = result._spans def truncate( self, max_width: int, *, overflow: Optional["OverflowMethod"] = None, pad: bool = False, ) -> None: """Truncate text if it is longer that a given width. Args: max_width (int): Maximum number of characters in text. overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None, to use self.overflow. pad (bool, optional): Pad with spaces if the length is less than max_width. Defaults to False. """ _overflow = overflow or self.overflow or DEFAULT_OVERFLOW if _overflow != "ignore": length = cell_len(self.plain) if length > max_width: if _overflow == "ellipsis": self.plain = set_cell_size(self.plain, max_width - 1) + "…" else: self.plain = set_cell_size(self.plain, max_width) if pad and length < max_width: spaces = max_width - length self._text = [f"{self.plain}{' ' * spaces}"] self._length = len(self.plain) def _trim_spans(self) -> None: """Remove or modify any spans that are over the end of the text.""" max_offset = len(self.plain) _Span = Span self._spans[:] = [ ( span if span.end < max_offset else _Span(span.start, min(max_offset, span.end), span.style) ) for span in self._spans if span.start < max_offset ] def pad(self, count: int, character: str = " ") -> None: """Pad left and right with a given number of characters. Args: count (int): Width of padding. """ assert len(character) == 1, "Character must be a string of length 1" if count: pad_characters = character * count self.plain = f"{pad_characters}{self.plain}{pad_characters}" _Span = Span self._spans[:] = [ _Span(start + count, end + count, style) for start, end, style in self._spans ] def pad_left(self, count: int, character: str = " ") -> None: """Pad the left with a given character. Args: count (int): Number of characters to pad. character (str, optional): Character to pad with. Defaults to " ". """ assert len(character) == 1, "Character must be a string of length 1" if count: self.plain = f"{character * count}{self.plain}" _Span = Span self._spans[:] = [ _Span(start + count, end + count, style) for start, end, style in self._spans ] def pad_right(self, count: int, character: str = " ") -> None: """Pad the right with a given character. Args: count (int): Number of characters to pad. character (str, optional): Character to pad with. Defaults to " ". """ assert len(character) == 1, "Character must be a string of length 1" if count: self.plain = f"{self.plain}{character * count}" def align(self, align: AlignMethod, width: int, character: str = " ") -> None: """Align text to a given width. Args: align (AlignMethod): One of "left", "center", or "right". width (int): Desired width. character (str, optional): Character to pad with. Defaults to " ". """ self.truncate(width) excess_space = width - cell_len(self.plain) if excess_space: if align == "left": self.pad_right(excess_space, character) elif align == "center": left = excess_space // 2 self.pad_left(left, character) self.pad_right(excess_space - left, character) else: self.pad_left(excess_space, character) def append( self, text: Union["Text", str], style: Optional[Union[str, "Style"]] = None ) -> "Text": """Add text with an optional style. Args: text (Union[Text, str]): A str or Text to append. style (str, optional): A style name. Defaults to None. Returns: Text: Returns self for chaining. """ if not isinstance(text, (str, Text)): raise TypeError("Only str or Text can be appended to Text") if len(text): if isinstance(text, str): text = strip_control_codes(text) self._text.append(text) offset = len(self) text_length = len(text) if style is not None: self._spans.append(Span(offset, offset + text_length, style)) self._length += text_length elif isinstance(text, Text): _Span = Span if style is not None: raise ValueError( "style must not be set when appending Text instance" ) text_length = self._length if text.style is not None: self._spans.append( _Span(text_length, text_length + len(text), text.style) ) self._text.append(text.plain) self._spans.extend( _Span(start + text_length, end + text_length, style) for start, end, style in text._spans ) self._length += len(text) return self def append_text(self, text: "Text") -> "Text": """Append another Text instance. This method is more performant that Text.append, but only works for Text. Returns: Text: Returns self for chaining. """ _Span = Span text_length = self._length if text.style is not None: self._spans.append(_Span(text_length, text_length + len(text), text.style)) self._text.append(text.plain) self._spans.extend( _Span(start + text_length, end + text_length, style) for start, end, style in text._spans ) self._length += len(text) return self def append_tokens( self, tokens: Iterable[Tuple[str, Optional[StyleType]]] ) -> "Text": """Append iterable of str and style. Style may be a Style instance or a str style definition. Args: pairs (Iterable[Tuple[str, Optional[StyleType]]]): An iterable of tuples containing str content and style. Returns: Text: Returns self for chaining. """ append_text = self._text.append append_span = self._spans.append _Span = Span offset = len(self) for content, style in tokens: append_text(content) if style is not None: append_span(_Span(offset, offset + len(content), style)) offset += len(content) self._length = offset return self def copy_styles(self, text: "Text") -> None: """Copy styles from another Text instance. Args: text (Text): A Text instance to copy styles from, must be the same length. """ self._spans.extend(text._spans) def split( self, separator: str = "\n", *, include_separator: bool = False, allow_blank: bool = False, ) -> Lines: """Split rich text in to lines, preserving styles. Args: separator (str, optional): String to split on. Defaults to "\\\\n". include_separator (bool, optional): Include the separator in the lines. Defaults to False. allow_blank (bool, optional): Return a blank line if the text ends with a separator. Defaults to False. Returns: List[RichText]: A list of rich text, one per line of the original. """ assert separator, "separator must not be empty" text = self.plain if separator not in text: return Lines([self.copy()]) if include_separator: lines = self.divide( match.end() for match in re.finditer(re.escape(separator), text) ) else: def flatten_spans() -> Iterable[int]: for match in re.finditer(re.escape(separator), text): start, end = match.span() yield start yield end lines = Lines( line for line in self.divide(flatten_spans()) if line.plain != separator ) if not allow_blank and text.endswith(separator): lines.pop() return lines def divide(self, offsets: Iterable[int]) -> Lines: """Divide text in to a number of lines at given offsets. Args: offsets (Iterable[int]): Offsets used to divide text. Returns: Lines: New RichText instances between offsets. """ _offsets = list(offsets) if not _offsets: return Lines([self.copy()]) text = self.plain text_length = len(text) divide_offsets = [0, *_offsets, text_length] line_ranges = list(zip(divide_offsets, divide_offsets[1:])) style = self.style justify = self.justify overflow = self.overflow _Text = Text new_lines = Lines( _Text( text[start:end], style=style, justify=justify, overflow=overflow, ) for start, end in line_ranges ) if not self._spans: return new_lines _line_appends = [line._spans.append for line in new_lines._lines] line_count = len(line_ranges) _Span = Span for span_start, span_end, style in self._spans: lower_bound = 0 upper_bound = line_count start_line_no = (lower_bound + upper_bound) // 2 while True: line_start, line_end = line_ranges[start_line_no] if span_start < line_start: upper_bound = start_line_no - 1 elif span_start > line_end: lower_bound = start_line_no + 1 else: break start_line_no = (lower_bound + upper_bound) // 2 if span_end < line_end: end_line_no = start_line_no else: end_line_no = lower_bound = start_line_no upper_bound = line_count while True: line_start, line_end = line_ranges[end_line_no] if span_end < line_start: upper_bound = end_line_no - 1 elif span_end > line_end: lower_bound = end_line_no + 1 else: break end_line_no = (lower_bound + upper_bound) // 2 for line_no in range(start_line_no, end_line_no + 1): line_start, line_end = line_ranges[line_no] new_start = max(0, span_start - line_start) new_end = min(span_end - line_start, line_end - line_start) if new_end > new_start: _line_appends[line_no](_Span(new_start, new_end, style)) return new_lines def right_crop(self, amount: int = 1) -> None: """Remove a number of characters from the end of the text.""" max_offset = len(self.plain) - amount _Span = Span self._spans[:] = [ ( span if span.end < max_offset else _Span(span.start, min(max_offset, span.end), span.style) ) for span in self._spans if span.start < max_offset ] self._text = [self.plain[:-amount]] self._length -= amount def wrap( self, console: "Console", width: int, *, justify: Optional["JustifyMethod"] = None, overflow: Optional["OverflowMethod"] = None, tab_size: int = 8, no_wrap: Optional[bool] = None, ) -> Lines: """Word wrap the text. Args: console (Console): Console instance. width (int): Number of characters per line. emoji (bool, optional): Also render emoji code. Defaults to True. justify (str, optional): Justify method: "default", "left", "center", "full", "right". Defaults to "default". overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. tab_size (int, optional): Default tab size. Defaults to 8. no_wrap (bool, optional): Disable wrapping, Defaults to False. Returns: Lines: Number of lines. """ wrap_justify = justify or self.justify or DEFAULT_JUSTIFY wrap_overflow = overflow or self.overflow or DEFAULT_OVERFLOW no_wrap = pick_bool(no_wrap, self.no_wrap, False) or overflow == "ignore" lines = Lines() for line in self.split(allow_blank=True): if "\t" in line: line.expand_tabs(tab_size) if no_wrap: new_lines = Lines([line]) else: offsets = divide_line(str(line), width, fold=wrap_overflow == "fold") new_lines = line.divide(offsets) for line in new_lines: line.rstrip_end(width) if wrap_justify: new_lines.justify( console, width, justify=wrap_justify, overflow=wrap_overflow ) for line in new_lines: line.truncate(width, overflow=wrap_overflow) lines.extend(new_lines) return lines def fit(self, width: int) -> Lines: """Fit the text in to given width by chopping in to lines. Args: width (int): Maximum characters in a line. Returns: Lines: List of lines. """ lines: Lines = Lines() append = lines.append for line in self.split(): line.set_length(width) append(line) return lines def detect_indentation(self) -> int: """Auto-detect indentation of code. Returns: int: Number of spaces used to indent code. """ _indentations = { len(match.group(1)) for match in re.finditer(r"^( *)(.*)$", self.plain, flags=re.MULTILINE) } try: indentation = ( reduce(gcd, [indent for indent in _indentations if not indent % 2]) or 1 ) except TypeError: indentation = 1 return indentation def with_indent_guides( self, indent_size: Optional[int] = None, *, character: str = "│", style: StyleType = "dim green", ) -> "Text": """Adds indent guide lines to text. Args: indent_size (Optional[int]): Size of indentation, or None to auto detect. Defaults to None. character (str, optional): Character to use for indentation. Defaults to "│". style (Union[Style, str], optional): Style of indent guides. Returns: Text: New text with indentation guides. """ _indent_size = self.detect_indentation() if indent_size is None else indent_size text = self.copy() text.expand_tabs() indent_line = f"{character}{' ' * (_indent_size - 1)}" re_indent = re.compile(r"^( *)(.*)$") new_lines: List[Text] = [] add_line = new_lines.append blank_lines = 0 for line in text.split(allow_blank=True): match = re_indent.match(line.plain) if not match or not match.group(2): blank_lines += 1 continue indent = match.group(1) full_indents, remaining_space = divmod(len(indent), _indent_size) new_indent = f"{indent_line * full_indents}{' ' * remaining_space}" line.plain = new_indent + line.plain[len(new_indent) :] line.stylize(style, 0, len(new_indent)) if blank_lines: new_lines.extend([Text(new_indent, style=style)] * blank_lines) blank_lines = 0 add_line(line) if blank_lines: new_lines.extend([Text("", style=style)] * blank_lines) new_text = text.blank_copy("\n").join(new_lines) return new_text if __name__ == "__main__": # pragma: no cover from pip._vendor.rich.console import Console text = Text( """\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n""" ) text.highlight_words(["Lorem"], "bold") text.highlight_words(["ipsum"], "italic") console = Console() console.rule("justify='left'") console.print(text, style="red") console.print() console.rule("justify='center'") console.print(text, style="green", justify="center") console.print() console.rule("justify='right'") console.print(text, style="blue", justify="right") console.print() console.rule("justify='full'") console.print(text, style="magenta", justify="full") console.print() _vendor/rich/traceback.py 0000644 00000062517 15030420735 0011430 0 ustar 00 from __future__ import absolute_import import os import platform import sys from dataclasses import dataclass, field from traceback import walk_tb from types import ModuleType, TracebackType from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, Union from pip._vendor.pygments.lexers import guess_lexer_for_filename from pip._vendor.pygments.token import Comment, Keyword, Name, Number, Operator, String from pip._vendor.pygments.token import Text as TextToken from pip._vendor.pygments.token import Token from . import pretty from ._loop import loop_first, loop_last from .columns import Columns from .console import Console, ConsoleOptions, ConsoleRenderable, RenderResult, group from .constrain import Constrain from .highlighter import RegexHighlighter, ReprHighlighter from .panel import Panel from .scope import render_scope from .style import Style from .syntax import Syntax from .text import Text from .theme import Theme WINDOWS = platform.system() == "Windows" LOCALS_MAX_LENGTH = 10 LOCALS_MAX_STRING = 80 def install( *, console: Optional[Console] = None, width: Optional[int] = 100, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, indent_guides: bool = True, suppress: Iterable[Union[str, ModuleType]] = (), max_frames: int = 100, ) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]: """Install a rich traceback handler. Once installed, any tracebacks will be printed with syntax highlighting and rich formatting. Args: console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance. width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100. extra_lines (int, optional): Extra lines of code. Defaults to 3. theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick a theme appropriate for the platform. word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. show_locals (bool, optional): Enable display of local variables. Defaults to False. indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. Returns: Callable: The previous exception handler that was replaced. """ traceback_console = Console(file=sys.stderr) if console is None else console def excepthook( type_: Type[BaseException], value: BaseException, traceback: Optional[TracebackType], ) -> None: traceback_console.print( Traceback.from_exception( type_, value, traceback, width=width, extra_lines=extra_lines, theme=theme, word_wrap=word_wrap, show_locals=show_locals, indent_guides=indent_guides, suppress=suppress, max_frames=max_frames, ) ) def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover tb_data = {} # store information about showtraceback call default_showtraceback = ip.showtraceback # keep reference of default traceback def ipy_show_traceback(*args: Any, **kwargs: Any) -> None: """wrap the default ip.showtraceback to store info for ip._showtraceback""" nonlocal tb_data tb_data = kwargs default_showtraceback(*args, **kwargs) def ipy_display_traceback( *args: Any, is_syntax: bool = False, **kwargs: Any ) -> None: """Internally called traceback from ip._showtraceback""" nonlocal tb_data exc_tuple = ip._get_exc_info() # do not display trace on syntax error tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2] # determine correct tb_offset compiled = tb_data.get("running_compiled_code", False) tb_offset = tb_data.get("tb_offset", 1 if compiled else 0) # remove ipython internal frames from trace with tb_offset for _ in range(tb_offset): if tb is None: break tb = tb.tb_next excepthook(exc_tuple[0], exc_tuple[1], tb) tb_data = {} # clear data upon usage # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work # this is also what the ipython docs recommends to modify when subclassing InteractiveShell ip._showtraceback = ipy_display_traceback # add wrapper to capture tb_data ip.showtraceback = ipy_show_traceback ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback( *args, is_syntax=True, **kwargs ) try: # pragma: no cover # if within ipython, use customized traceback ip = get_ipython() # type: ignore ipy_excepthook_closure(ip) return sys.excepthook except Exception: # otherwise use default system hook old_excepthook = sys.excepthook sys.excepthook = excepthook return old_excepthook @dataclass class Frame: filename: str lineno: int name: str line: str = "" locals: Optional[Dict[str, pretty.Node]] = None @dataclass class _SyntaxError: offset: int filename: str line: str lineno: int msg: str @dataclass class Stack: exc_type: str exc_value: str syntax_error: Optional[_SyntaxError] = None is_cause: bool = False frames: List[Frame] = field(default_factory=list) @dataclass class Trace: stacks: List[Stack] class PathHighlighter(RegexHighlighter): highlights = [r"(?P<dim>.*/)(?P<bold>.+)"] class Traceback: """A Console renderable that renders a traceback. Args: trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses the last exception. width (Optional[int], optional): Number of characters used to traceback. Defaults to 100. extra_lines (int, optional): Additional lines of code to render. Defaults to 3. theme (str, optional): Override pygments theme used in traceback. word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. show_locals (bool, optional): Enable display of local variables. Defaults to False. indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. Defaults to 10. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. """ LEXERS = { "": "text", ".py": "python", ".pxd": "cython", ".pyx": "cython", ".pxi": "pyrex", } def __init__( self, trace: Optional[Trace] = None, width: Optional[int] = 100, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, indent_guides: bool = True, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, suppress: Iterable[Union[str, ModuleType]] = (), max_frames: int = 100, ): if trace is None: exc_type, exc_value, traceback = sys.exc_info() if exc_type is None or exc_value is None or traceback is None: raise ValueError( "Value for 'trace' required if not called in except: block" ) trace = self.extract( exc_type, exc_value, traceback, show_locals=show_locals ) self.trace = trace self.width = width self.extra_lines = extra_lines self.theme = Syntax.get_theme(theme or "ansi_dark") self.word_wrap = word_wrap self.show_locals = show_locals self.indent_guides = indent_guides self.locals_max_length = locals_max_length self.locals_max_string = locals_max_string self.suppress: Sequence[str] = [] for suppress_entity in suppress: if not isinstance(suppress_entity, str): assert ( suppress_entity.__file__ is not None ), f"{suppress_entity!r} must be a module with '__file__' attribute" path = os.path.dirname(suppress_entity.__file__) else: path = suppress_entity path = os.path.normpath(os.path.abspath(path)) self.suppress.append(path) self.max_frames = max(4, max_frames) if max_frames > 0 else 0 @classmethod def from_exception( cls, exc_type: Type[Any], exc_value: BaseException, traceback: Optional[TracebackType], width: Optional[int] = 100, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, indent_guides: bool = True, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, suppress: Iterable[Union[str, ModuleType]] = (), max_frames: int = 100, ) -> "Traceback": """Create a traceback from exception info Args: exc_type (Type[BaseException]): Exception type. exc_value (BaseException): Exception value. traceback (TracebackType): Python Traceback object. width (Optional[int], optional): Number of characters used to traceback. Defaults to 100. extra_lines (int, optional): Additional lines of code to render. Defaults to 3. theme (str, optional): Override pygments theme used in traceback. word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. show_locals (bool, optional): Enable display of local variables. Defaults to False. indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. Defaults to 10. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. Returns: Traceback: A Traceback instance that may be printed. """ rich_traceback = cls.extract( exc_type, exc_value, traceback, show_locals=show_locals ) return cls( rich_traceback, width=width, extra_lines=extra_lines, theme=theme, word_wrap=word_wrap, show_locals=show_locals, indent_guides=indent_guides, locals_max_length=locals_max_length, locals_max_string=locals_max_string, suppress=suppress, max_frames=max_frames, ) @classmethod def extract( cls, exc_type: Type[BaseException], exc_value: BaseException, traceback: Optional[TracebackType], show_locals: bool = False, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, ) -> Trace: """Extract traceback information. Args: exc_type (Type[BaseException]): Exception type. exc_value (BaseException): Exception value. traceback (TracebackType): Python Traceback object. show_locals (bool, optional): Enable display of local variables. Defaults to False. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. Defaults to 10. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80. Returns: Trace: A Trace instance which you can use to construct a `Traceback`. """ stacks: List[Stack] = [] is_cause = False from pip._vendor.rich import _IMPORT_CWD def safe_str(_object: Any) -> str: """Don't allow exceptions from __str__ to propegate.""" try: return str(_object) except Exception: return "<exception str() failed>" while True: stack = Stack( exc_type=safe_str(exc_type.__name__), exc_value=safe_str(exc_value), is_cause=is_cause, ) if isinstance(exc_value, SyntaxError): stack.syntax_error = _SyntaxError( offset=exc_value.offset or 0, filename=exc_value.filename or "?", lineno=exc_value.lineno or 0, line=exc_value.text or "", msg=exc_value.msg, ) stacks.append(stack) append = stack.frames.append for frame_summary, line_no in walk_tb(traceback): filename = frame_summary.f_code.co_filename if filename and not filename.startswith("<"): if not os.path.isabs(filename): filename = os.path.join(_IMPORT_CWD, filename) frame = Frame( filename=filename or "?", lineno=line_no, name=frame_summary.f_code.co_name, locals={ key: pretty.traverse( value, max_length=locals_max_length, max_string=locals_max_string, ) for key, value in frame_summary.f_locals.items() } if show_locals else None, ) append(frame) if "_rich_traceback_guard" in frame_summary.f_locals: del stack.frames[:] cause = getattr(exc_value, "__cause__", None) if cause and cause.__traceback__: exc_type = cause.__class__ exc_value = cause traceback = cause.__traceback__ if traceback: is_cause = True continue cause = exc_value.__context__ if ( cause and cause.__traceback__ and not getattr(exc_value, "__suppress_context__", False) ): exc_type = cause.__class__ exc_value = cause traceback = cause.__traceback__ if traceback: is_cause = False continue # No cover, code is reached but coverage doesn't recognize it. break # pragma: no cover trace = Trace(stacks=stacks) return trace def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: theme = self.theme background_style = theme.get_background_style() token_style = theme.get_style_for_token traceback_theme = Theme( { "pretty": token_style(TextToken), "pygments.text": token_style(Token), "pygments.string": token_style(String), "pygments.function": token_style(Name.Function), "pygments.number": token_style(Number), "repr.indent": token_style(Comment) + Style(dim=True), "repr.str": token_style(String), "repr.brace": token_style(TextToken) + Style(bold=True), "repr.number": token_style(Number), "repr.bool_true": token_style(Keyword.Constant), "repr.bool_false": token_style(Keyword.Constant), "repr.none": token_style(Keyword.Constant), "scope.border": token_style(String.Delimiter), "scope.equals": token_style(Operator), "scope.key": token_style(Name), "scope.key.special": token_style(Name.Constant) + Style(dim=True), }, inherit=False, ) highlighter = ReprHighlighter() for last, stack in loop_last(reversed(self.trace.stacks)): if stack.frames: stack_renderable: ConsoleRenderable = Panel( self._render_stack(stack), title="[traceback.title]Traceback [dim](most recent call last)", style=background_style, border_style="traceback.border", expand=True, padding=(0, 1), ) stack_renderable = Constrain(stack_renderable, self.width) with console.use_theme(traceback_theme): yield stack_renderable if stack.syntax_error is not None: with console.use_theme(traceback_theme): yield Constrain( Panel( self._render_syntax_error(stack.syntax_error), style=background_style, border_style="traceback.border.syntax_error", expand=True, padding=(0, 1), width=self.width, ), self.width, ) yield Text.assemble( (f"{stack.exc_type}: ", "traceback.exc_type"), highlighter(stack.syntax_error.msg), ) elif stack.exc_value: yield Text.assemble( (f"{stack.exc_type}: ", "traceback.exc_type"), highlighter(stack.exc_value), ) else: yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type")) if not last: if stack.is_cause: yield Text.from_markup( "\n[i]The above exception was the direct cause of the following exception:\n", ) else: yield Text.from_markup( "\n[i]During handling of the above exception, another exception occurred:\n", ) @group() def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult: highlighter = ReprHighlighter() path_highlighter = PathHighlighter() if syntax_error.filename != "<stdin>": text = Text.assemble( (f" {syntax_error.filename}", "pygments.string"), (":", "pygments.text"), (str(syntax_error.lineno), "pygments.number"), style="pygments.text", ) yield path_highlighter(text) syntax_error_text = highlighter(syntax_error.line.rstrip()) syntax_error_text.no_wrap = True offset = min(syntax_error.offset - 1, len(syntax_error_text)) syntax_error_text.stylize("bold underline", offset, offset) syntax_error_text += Text.from_markup( "\n" + " " * offset + "[traceback.offset]▲[/]", style="pygments.text", ) yield syntax_error_text @classmethod def _guess_lexer(cls, filename: str, code: str) -> str: ext = os.path.splitext(filename)[-1] if not ext: # No extension, look at first line to see if it is a hashbang # Note, this is an educated guess and not a guarantee # If it fails, the only downside is that the code is highlighted strangely new_line_index = code.index("\n") first_line = code[:new_line_index] if new_line_index != -1 else code if first_line.startswith("#!") and "python" in first_line.lower(): return "python" lexer_name = ( cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name ) return lexer_name @group() def _render_stack(self, stack: Stack) -> RenderResult: path_highlighter = PathHighlighter() theme = self.theme code_cache: Dict[str, str] = {} def read_code(filename: str) -> str: """Read files, and cache results on filename. Args: filename (str): Filename to read Returns: str: Contents of file """ code = code_cache.get(filename) if code is None: with open( filename, "rt", encoding="utf-8", errors="replace" ) as code_file: code = code_file.read() code_cache[filename] = code return code def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]: if frame.locals: yield render_scope( frame.locals, title="locals", indent_guides=self.indent_guides, max_length=self.locals_max_length, max_string=self.locals_max_string, ) exclude_frames: Optional[range] = None if self.max_frames != 0: exclude_frames = range( self.max_frames // 2, len(stack.frames) - self.max_frames // 2, ) excluded = False for frame_index, frame in enumerate(stack.frames): if exclude_frames and frame_index in exclude_frames: excluded = True continue if excluded: assert exclude_frames is not None yield Text( f"\n... {len(exclude_frames)} frames hidden ...", justify="center", style="traceback.error", ) excluded = False first = frame_index == 1 frame_filename = frame.filename suppressed = any(frame_filename.startswith(path) for path in self.suppress) text = Text.assemble( path_highlighter(Text(frame.filename, style="pygments.string")), (":", "pygments.text"), (str(frame.lineno), "pygments.number"), " in ", (frame.name, "pygments.function"), style="pygments.text", ) if not frame.filename.startswith("<") and not first: yield "" yield text if frame.filename.startswith("<"): yield from render_locals(frame) continue if not suppressed: try: code = read_code(frame.filename) lexer_name = self._guess_lexer(frame.filename, code) syntax = Syntax( code, lexer_name, theme=theme, line_numbers=True, line_range=( frame.lineno - self.extra_lines, frame.lineno + self.extra_lines, ), highlight_lines={frame.lineno}, word_wrap=self.word_wrap, code_width=88, indent_guides=self.indent_guides, dedent=False, ) yield "" except Exception as error: yield Text.assemble( (f"\n{error}", "traceback.error"), ) else: yield ( Columns( [ syntax, *render_locals(frame), ], padding=1, ) if frame.locals else syntax ) if __name__ == "__main__": # pragma: no cover from .console import Console console = Console() import sys def bar(a: Any) -> None: # 这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑 one = 1 print(one / a) def foo(a: Any) -> None: _rich_traceback_guard = True zed = { "characters": { "Paul Atreides", "Vladimir Harkonnen", "Thufir Hawat", "Duncan Idaho", }, "atomic_types": (None, False, True), } bar(a) def error() -> None: try: try: foo(0) except: slfkjsldkfj # type: ignore except: console.print_exception(show_locals=True) error() _vendor/rich/segment.py 0000644 00000056554 15030420735 0011157 0 ustar 00 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, 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 ControlCode = Union[ Tuple[ControlType], Tuple[ControlType, int], 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. """ text: str = "" """Raw text.""" style: Optional[Style] = None """An optional style.""" control: Optional[Sequence[ControlCode]] = None """Optional sequence of control codes.""" 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 cell_length(self) -> int: """Get cell length of segment.""" return 0 if self.control else cell_len(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"]: # type: ignore 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)) before = text[:pos] cell_pos = cell_len(before) if cell_pos == cut: return ( _Segment(before, style, control), _Segment(text[pos:], style, control), ) while pos < len(text): char = text[pos] pos += 1 cell_pos += cell_size(char) before = text[:pos] if cell_pos == cut: return ( _Segment(before, style, control), _Segment(text[pos:], style, control), ) if cell_pos > cut: return ( _Segment(before[: pos - 1] + " ", style, control), _Segment(" " + text[pos:], style, control), ) 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. Returns: Tuple[Segment, Segment]: Two segments. """ text, style, control = self if _is_single_cell_widths(text): # Fast path with all 1 cell characters if cut >= len(text): return self, Segment("", style, control) return ( Segment(text[:cut], style, control), Segment(text[cut:], style, control), ) return self._split_cells(self, cut) @classmethod 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, style, _ = segment while text: _text, new_line, text = text.partition("\n") if _text: append(cls(_text, 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 del line[:] 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(segment.text) for segment in line) @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: try: cut = next(iter_cuts) except StopIteration: return [] if cut != 0: break yield [] pos = 0 for segment in segments: while segment.text: end_pos = pos + segment.cell_length if end_pos < cut: add_segment(segment) pos = end_pos break try: if end_pos == cut: add_segment(segment) yield split_segments[:] del split_segments[:] pos = end_pos break else: before, segment = segment.split_cells(cut - pos) add_segment(before) yield split_segments[:] del split_segments[:] pos = cut finally: try: cut = next(iter_cuts) except StopIteration: if split_segments: yield split_segments[:] return yield split_segments[:] 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__": 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 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." ) _vendor/rich/live_render.py 0000644 00000007123 15030420735 0011777 0 ustar 00 import sys from typing import Optional, Tuple if sys.version_info >= (3, 8): from typing import Literal else: from pip._vendor.typing_extensions import Literal # pragma: no cover from ._loop import loop_last from .console import Console, ConsoleOptions, RenderableType, RenderResult from .control import Control from .segment import ControlType, Segment from .style import StyleType from .text import Text VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"] class LiveRender: """Creates a renderable that may be updated. Args: renderable (RenderableType): Any renderable object. style (StyleType, optional): An optional style to apply to the renderable. Defaults to "". """ def __init__( self, renderable: RenderableType, style: StyleType = "", vertical_overflow: VerticalOverflowMethod = "ellipsis", ) -> None: self.renderable = renderable self.style = style self.vertical_overflow = vertical_overflow self._shape: Optional[Tuple[int, int]] = None def set_renderable(self, renderable: RenderableType) -> None: """Set a new renderable. Args: renderable (RenderableType): Any renderable object, including str. """ self.renderable = renderable def position_cursor(self) -> Control: """Get control codes to move cursor to beginning of live render. Returns: Control: A control instance that may be printed. """ if self._shape is not None: _, height = self._shape return Control( ControlType.CARRIAGE_RETURN, (ControlType.ERASE_IN_LINE, 2), *( ( (ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2), ) * (height - 1) ) ) return Control() def restore_cursor(self) -> Control: """Get control codes to clear the render and restore the cursor to its previous position. Returns: Control: A Control instance that may be printed. """ if self._shape is not None: _, height = self._shape return Control( ControlType.CARRIAGE_RETURN, *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height ) return Control() def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: renderable = self.renderable style = console.get_style(self.style) lines = console.render_lines(renderable, options, style=style, pad=False) shape = Segment.get_shape(lines) _, height = shape if height > options.size.height: if self.vertical_overflow == "crop": lines = lines[: options.size.height] shape = Segment.get_shape(lines) elif self.vertical_overflow == "ellipsis": lines = lines[: (options.size.height - 1)] overflow_text = Text( "...", overflow="crop", justify="center", end="", style="live.ellipsis", ) lines.append(list(console.render(overflow_text))) shape = Segment.get_shape(lines) self._shape = shape new_line = Segment.line() for last, line in loop_last(lines): yield from line if not last: yield new_line _vendor/rich/style.py 0000644 00000063545 15030420735 0010653 0 ustar 00 import sys from functools import lru_cache from marshal import loads, dumps from random import randint from typing import Any, cast, Dict, Iterable, List, Optional, Type, Union from . import errors from .color import Color, ColorParseError, ColorSystem, blend_rgb from .repr import rich_repr, Result from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme # 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: 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._link_id = f"{randint(0, 999999)}" if link else "" self._meta = None if meta is None else dumps(meta) self._hash = hash( ( self._color, self._bgcolor, self._attributes, self._set_attributes, link, self._meta, ) ) 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._hash = hash( ( color, bgcolor, None, None, None, None, ) ) style._null = not (color or bgcolor) 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._link_id = "" style._meta = dumps(meta) style._hash = hash( ( None, None, None, None, None, style._meta, ) ) 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 (Optiona[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._color == other._color and self._bgcolor == other._bgcolor and self._set_attributes == other._set_attributes and self._attributes == other._attributes and self._link == other._link and self._meta == other._meta ) def __hash__(self) -> int: 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._hash = self._hash style._null = False style._meta = 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) is None 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 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 = self._hash 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._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") def __add__(self, style: Optional["Style"]) -> "Style": if not (isinstance(style, Style) or style is None): return NotImplemented 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._hash = style._hash new_style._null = self._null or 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 return new_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] _vendor/rich/progress_bar.py 0000644 00000017122 15030420735 0012171 0 ustar 00 import math from functools import lru_cache from time import monotonic from typing import Iterable, List, Optional from .color import Color, blend_rgb from .color_triplet import ColorTriplet from .console import Console, ConsoleOptions, RenderResult from .jupyter import JupyterMixin from .measure import Measurement from .segment import Segment from .style import Style, StyleType # Number of characters before 'pulse' animation repeats PULSE_SIZE = 20 class ProgressBar(JupyterMixin): """Renders a (progress) bar. Used by rich.progress. Args: total (float, optional): Number of steps in the bar. Defaults to 100. completed (float, optional): Number of steps completed. Defaults to 0. width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None. pulse (bool, optional): Enable pulse effect. Defaults to False. style (StyleType, optional): Style for the bar background. Defaults to "bar.back". complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done". pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". animation_time (Optional[float], optional): Time in seconds to use for animation, or None to use system time. """ def __init__( self, total: float = 100.0, completed: float = 0, width: Optional[int] = None, pulse: bool = False, style: StyleType = "bar.back", complete_style: StyleType = "bar.complete", finished_style: StyleType = "bar.finished", pulse_style: StyleType = "bar.pulse", animation_time: Optional[float] = None, ): self.total = total self.completed = completed self.width = width self.pulse = pulse self.style = style self.complete_style = complete_style self.finished_style = finished_style self.pulse_style = pulse_style self.animation_time = animation_time self._pulse_segments: Optional[List[Segment]] = None def __repr__(self) -> str: return f"<Bar {self.completed!r} of {self.total!r}>" @property def percentage_completed(self) -> float: """Calculate percentage complete.""" completed = (self.completed / self.total) * 100.0 completed = min(100, max(0.0, completed)) return completed @lru_cache(maxsize=16) def _get_pulse_segments( self, fore_style: Style, back_style: Style, color_system: str, no_color: bool, ascii: bool = False, ) -> List[Segment]: """Get a list of segments to render a pulse animation. Returns: List[Segment]: A list of segments, one segment per character. """ bar = "-" if ascii else "━" segments: List[Segment] = [] if color_system not in ("standard", "eight_bit", "truecolor") or no_color: segments += [Segment(bar, fore_style)] * (PULSE_SIZE // 2) segments += [Segment(" " if no_color else bar, back_style)] * ( PULSE_SIZE - (PULSE_SIZE // 2) ) return segments append = segments.append fore_color = ( fore_style.color.get_truecolor() if fore_style.color else ColorTriplet(255, 0, 255) ) back_color = ( back_style.color.get_truecolor() if back_style.color else ColorTriplet(0, 0, 0) ) cos = math.cos pi = math.pi _Segment = Segment _Style = Style from_triplet = Color.from_triplet for index in range(PULSE_SIZE): position = index / PULSE_SIZE fade = 0.5 + cos((position * pi * 2)) / 2.0 color = blend_rgb(fore_color, back_color, cross_fade=fade) append(_Segment(bar, _Style(color=from_triplet(color)))) return segments def update(self, completed: float, total: Optional[float] = None) -> None: """Update progress with new values. Args: completed (float): Number of steps completed. total (float, optional): Total number of steps, or ``None`` to not change. Defaults to None. """ self.completed = completed self.total = total if total is not None else self.total def _render_pulse( self, console: Console, width: int, ascii: bool = False ) -> Iterable[Segment]: """Renders the pulse animation. Args: console (Console): Console instance. width (int): Width in characters of pulse animation. Returns: RenderResult: [description] Yields: Iterator[Segment]: Segments to render pulse """ fore_style = console.get_style(self.pulse_style, default="white") back_style = console.get_style(self.style, default="black") pulse_segments = self._get_pulse_segments( fore_style, back_style, console.color_system, console.no_color, ascii=ascii ) segment_count = len(pulse_segments) current_time = ( monotonic() if self.animation_time is None else self.animation_time ) segments = pulse_segments * (int(width / segment_count) + 2) offset = int(-current_time * 15) % segment_count segments = segments[offset : offset + width] yield from segments def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: width = min(self.width or options.max_width, options.max_width) ascii = options.legacy_windows or options.ascii_only if self.pulse: yield from self._render_pulse(console, width, ascii=ascii) return completed = min(self.total, max(0, self.completed)) bar = "-" if ascii else "━" half_bar_right = " " if ascii else "╸" half_bar_left = " " if ascii else "╺" complete_halves = ( int(width * 2 * completed / self.total) if self.total else width * 2 ) bar_count = complete_halves // 2 half_bar_count = complete_halves % 2 style = console.get_style(self.style) complete_style = console.get_style( self.complete_style if self.completed < self.total else self.finished_style ) _Segment = Segment if bar_count: yield _Segment(bar * bar_count, complete_style) if half_bar_count: yield _Segment(half_bar_right * half_bar_count, complete_style) if not console.no_color: remaining_bars = width - bar_count - half_bar_count if remaining_bars and console.color_system is not None: if not half_bar_count and bar_count: yield _Segment(half_bar_left, style) remaining_bars -= 1 if remaining_bars: yield _Segment(bar * remaining_bars, style) 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) ) if __name__ == "__main__": # pragma: no cover console = Console() bar = ProgressBar(width=50, total=100) import time console.show_cursor(False) for n in range(0, 101, 1): bar.update(n) console.print(bar) console.file.write("\r") time.sleep(0.05) console.show_cursor(True) console.print() _vendor/rich/json.py 0000644 00000011673 15030420735 0010457 0 ustar 00 from json import loads, dumps from typing import Any, Callable, Optional, Union from .text import Text from .highlighter import JSONHighlighter, NullHighlighter class JSON: """A renderable which pretty prints JSON. Args: json (str): JSON encoded data. indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2. highlight (bool, optional): Enable highlighting. Defaults to True. skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. check_circular (bool, optional): Check for circular references. Defaults to True. allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. default (Callable, optional): A callable that converts values that can not be encoded in to something that can be JSON encoded. Defaults to None. sort_keys (bool, optional): Sort dictionary keys. Defaults to False. """ def __init__( self, json: str, indent: Union[None, int, str] = 2, highlight: bool = True, skip_keys: bool = False, ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, default: Optional[Callable[[Any], Any]] = None, sort_keys: bool = False, ) -> None: data = loads(json) json = dumps( data, indent=indent, skipkeys=skip_keys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, default=default, sort_keys=sort_keys, ) highlighter = JSONHighlighter() if highlight else NullHighlighter() self.text = highlighter(json) self.text.no_wrap = True self.text.overflow = None @classmethod def from_data( cls, data: Any, indent: Union[None, int, str] = 2, highlight: bool = True, skip_keys: bool = False, ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, default: Optional[Callable[[Any], Any]] = None, sort_keys: bool = False, ) -> "JSON": """Encodes a JSON object from arbitrary data. Args: data (Any): An object that may be encoded in to JSON indent (Union[None, int, str], optional): Number of characters to indent by. Defaults to 2. highlight (bool, optional): Enable highlighting. Defaults to True. default (Callable, optional): Optional callable which will be called for objects that cannot be serialized. Defaults to None. skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. check_circular (bool, optional): Check for circular references. Defaults to True. allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. default (Callable, optional): A callable that converts values that can not be encoded in to something that can be JSON encoded. Defaults to None. sort_keys (bool, optional): Sort dictionary keys. Defaults to False. Returns: JSON: New JSON object from the given data. """ json_instance: "JSON" = cls.__new__(cls) json = dumps( data, indent=indent, skipkeys=skip_keys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, default=default, sort_keys=sort_keys, ) highlighter = JSONHighlighter() if highlight else NullHighlighter() json_instance.text = highlighter(json) json_instance.text.no_wrap = True json_instance.text.overflow = None return json_instance def __rich__(self) -> Text: return self.text if __name__ == "__main__": import argparse import sys parser = argparse.ArgumentParser(description="Pretty print json") parser.add_argument( "path", metavar="PATH", help="path to file, or - for stdin", ) parser.add_argument( "-i", "--indent", metavar="SPACES", type=int, help="Number of spaces in an indent", default=2, ) args = parser.parse_args() from pip._vendor.rich.console import Console console = Console() error_console = Console(stderr=True) try: if args.path == "-": json_data = sys.stdin.read() else: with open(args.path, "rt") as json_file: json_data = json_file.read() except Exception as error: error_console.print(f"Unable to read {args.path!r}; {error}") sys.exit(-1) console.print(JSON(json_data, indent=args.indent), soft_wrap=True) _vendor/rich/screen.py 0000644 00000003067 15030420735 0010763 0 ustar 00 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 _vendor/rich/_spinners.py 0000644 00000063631 15030420735 0011507 0 ustar 00 """ Spinners are from: * cli-spinners: MIT License Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ SPINNERS = { "dots": { "interval": 80, "frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], }, "dots2": {"interval": 80, "frames": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]}, "dots3": { "interval": 80, "frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"], }, "dots4": { "interval": 80, "frames": [ "⠄", "⠆", "⠇", "⠋", "⠙", "⠸", "⠰", "⠠", "⠰", "⠸", "⠙", "⠋", "⠇", "⠆", ], }, "dots5": { "interval": 80, "frames": [ "⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", ], }, "dots6": { "interval": 80, "frames": [ "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁", ], }, "dots7": { "interval": 80, "frames": [ "⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", ], }, "dots8": { "interval": 80, "frames": [ "⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈", ], }, "dots9": {"interval": 80, "frames": ["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"]}, "dots10": {"interval": 80, "frames": ["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"]}, "dots11": {"interval": 100, "frames": ["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"]}, "dots12": { "interval": 80, "frames": [ "⢀⠀", "⡀⠀", "⠄⠀", "⢂⠀", "⡂⠀", "⠅⠀", "⢃⠀", "⡃⠀", "⠍⠀", "⢋⠀", "⡋⠀", "⠍⠁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⢈⠩", "⡀⢙", "⠄⡙", "⢂⠩", "⡂⢘", "⠅⡘", "⢃⠨", "⡃⢐", "⠍⡐", "⢋⠠", "⡋⢀", "⠍⡁", "⢋⠁", "⡋⠁", "⠍⠉", "⠋⠉", "⠋⠉", "⠉⠙", "⠉⠙", "⠉⠩", "⠈⢙", "⠈⡙", "⠈⠩", "⠀⢙", "⠀⡙", "⠀⠩", "⠀⢘", "⠀⡘", "⠀⠨", "⠀⢐", "⠀⡐", "⠀⠠", "⠀⢀", "⠀⡀", ], }, "dots8Bit": { "interval": 80, "frames": [ "⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏", "⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟", "⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯", "⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿", "⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇", "⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏", "⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗", "⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟", "⢠", "⢡", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯", "⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿", ], }, "line": {"interval": 130, "frames": ["-", "\\", "|", "/"]}, "line2": {"interval": 100, "frames": ["⠂", "-", "–", "—", "–", "-"]}, "pipe": {"interval": 100, "frames": ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"]}, "simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]}, "simpleDotsScrolling": { "interval": 200, "frames": [". ", ".. ", "...", " ..", " .", " "], }, "star": {"interval": 70, "frames": ["✶", "✸", "✹", "✺", "✹", "✷"]}, "star2": {"interval": 80, "frames": ["+", "x", "*"]}, "flip": { "interval": 70, "frames": ["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"], }, "hamburger": {"interval": 100, "frames": ["☱", "☲", "☴"]}, "growVertical": { "interval": 120, "frames": ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"], }, "growHorizontal": { "interval": 120, "frames": ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "▊", "▋", "▌", "▍", "▎"], }, "balloon": {"interval": 140, "frames": [" ", ".", "o", "O", "@", "*", " "]}, "balloon2": {"interval": 120, "frames": [".", "o", "O", "°", "O", "o", "."]}, "noise": {"interval": 100, "frames": ["▓", "▒", "░"]}, "bounce": {"interval": 120, "frames": ["⠁", "⠂", "⠄", "⠂"]}, "boxBounce": {"interval": 120, "frames": ["▖", "▘", "▝", "▗"]}, "boxBounce2": {"interval": 100, "frames": ["▌", "▀", "▐", "▄"]}, "triangle": {"interval": 50, "frames": ["◢", "◣", "◤", "◥"]}, "arc": {"interval": 100, "frames": ["◜", "◠", "◝", "◞", "◡", "◟"]}, "circle": {"interval": 120, "frames": ["◡", "⊙", "◠"]}, "squareCorners": {"interval": 180, "frames": ["◰", "◳", "◲", "◱"]}, "circleQuarters": {"interval": 120, "frames": ["◴", "◷", "◶", "◵"]}, "circleHalves": {"interval": 50, "frames": ["◐", "◓", "◑", "◒"]}, "squish": {"interval": 100, "frames": ["╫", "╪"]}, "toggle": {"interval": 250, "frames": ["⊶", "⊷"]}, "toggle2": {"interval": 80, "frames": ["▫", "▪"]}, "toggle3": {"interval": 120, "frames": ["□", "■"]}, "toggle4": {"interval": 100, "frames": ["■", "□", "▪", "▫"]}, "toggle5": {"interval": 100, "frames": ["▮", "▯"]}, "toggle6": {"interval": 300, "frames": ["ဝ", "၀"]}, "toggle7": {"interval": 80, "frames": ["⦾", "⦿"]}, "toggle8": {"interval": 100, "frames": ["◍", "◌"]}, "toggle9": {"interval": 100, "frames": ["◉", "◎"]}, "toggle10": {"interval": 100, "frames": ["㊂", "㊀", "㊁"]}, "toggle11": {"interval": 50, "frames": ["⧇", "⧆"]}, "toggle12": {"interval": 120, "frames": ["☗", "☖"]}, "toggle13": {"interval": 80, "frames": ["=", "*", "-"]}, "arrow": {"interval": 100, "frames": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"]}, "arrow2": { "interval": 80, "frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "], }, "arrow3": { "interval": 120, "frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"], }, "bouncingBar": { "interval": 80, "frames": [ "[ ]", "[= ]", "[== ]", "[=== ]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]", ], }, "bouncingBall": { "interval": 80, "frames": [ "( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )", "(● )", ], }, "smiley": {"interval": 200, "frames": ["😄 ", "😝 "]}, "monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]}, "hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]}, "clock": { "interval": 100, "frames": [ "🕛 ", "🕐 ", "🕑 ", "🕒 ", "🕓 ", "🕔 ", "🕕 ", "🕖 ", "🕗 ", "🕘 ", "🕙 ", "🕚 ", ], }, "earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]}, "material": { "interval": 17, "frames": [ "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "███████▁▁▁▁▁▁▁▁▁▁▁▁▁", "████████▁▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "██████████▁▁▁▁▁▁▁▁▁▁", "███████████▁▁▁▁▁▁▁▁▁", "█████████████▁▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁▁██████████████▁▁▁▁", "▁▁▁██████████████▁▁▁", "▁▁▁▁█████████████▁▁▁", "▁▁▁▁██████████████▁▁", "▁▁▁▁██████████████▁▁", "▁▁▁▁▁██████████████▁", "▁▁▁▁▁██████████████▁", "▁▁▁▁▁██████████████▁", "▁▁▁▁▁▁██████████████", "▁▁▁▁▁▁██████████████", "▁▁▁▁▁▁▁█████████████", "▁▁▁▁▁▁▁█████████████", "▁▁▁▁▁▁▁▁████████████", "▁▁▁▁▁▁▁▁████████████", "▁▁▁▁▁▁▁▁▁███████████", "▁▁▁▁▁▁▁▁▁███████████", "▁▁▁▁▁▁▁▁▁▁██████████", "▁▁▁▁▁▁▁▁▁▁██████████", "▁▁▁▁▁▁▁▁▁▁▁▁████████", "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█", "████████▁▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "█████████▁▁▁▁▁▁▁▁▁▁▁", "███████████▁▁▁▁▁▁▁▁▁", "████████████▁▁▁▁▁▁▁▁", "████████████▁▁▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "██████████████▁▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁██████████████▁▁▁▁▁", "▁▁▁█████████████▁▁▁▁", "▁▁▁▁▁████████████▁▁▁", "▁▁▁▁▁████████████▁▁▁", "▁▁▁▁▁▁███████████▁▁▁", "▁▁▁▁▁▁▁▁█████████▁▁▁", "▁▁▁▁▁▁▁▁█████████▁▁▁", "▁▁▁▁▁▁▁▁▁█████████▁▁", "▁▁▁▁▁▁▁▁▁█████████▁▁", "▁▁▁▁▁▁▁▁▁▁█████████▁", "▁▁▁▁▁▁▁▁▁▁▁████████▁", "▁▁▁▁▁▁▁▁▁▁▁████████▁", "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", ], }, "moon": { "interval": 80, "frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "], }, "runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]}, "pong": { "interval": 80, "frames": [ "▐⠂ ▌", "▐⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂▌", "▐ ⠠▌", "▐ ⡀▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐ ⠠ ▌", "▐ ⠂ ▌", "▐ ⠈ ▌", "▐ ⠂ ▌", "▐ ⠠ ▌", "▐ ⡀ ▌", "▐⠠ ▌", ], }, "shark": { "interval": 120, "frames": [ "▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌", ], }, "dqpb": {"interval": 100, "frames": ["d", "q", "p", "b"]}, "weather": { "interval": 100, "frames": [ "☀️ ", "☀️ ", "☀️ ", "🌤 ", "⛅️ ", "🌥 ", "☁️ ", "🌧 ", "🌨 ", "🌧 ", "🌨 ", "🌧 ", "🌨 ", "⛈ ", "🌨 ", "🌧 ", "🌨 ", "☁️ ", "🌥 ", "⛅️ ", "🌤 ", "☀️ ", "☀️ ", ], }, "christmas": {"interval": 400, "frames": ["🌲", "🎄"]}, "grenade": { "interval": 80, "frames": [ "، ", "′ ", " ´ ", " ‾ ", " ⸌", " ⸊", " |", " ⁎", " ⁕", " ෴ ", " ⁓", " ", " ", " ", ], }, "point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]}, "layer": {"interval": 150, "frames": ["-", "=", "≡"]}, "betaWave": { "interval": 80, "frames": [ "ρββββββ", "βρβββββ", "ββρββββ", "βββρβββ", "ββββρββ", "βββββρβ", "ββββββρ", ], }, "aesthetic": { "interval": 80, "frames": [ "▰▱▱▱▱▱▱", "▰▰▱▱▱▱▱", "▰▰▰▱▱▱▱", "▰▰▰▰▱▱▱", "▰▰▰▰▰▱▱", "▰▰▰▰▰▰▱", "▰▰▰▰▰▰▰", "▰▱▱▱▱▱▱", ], }, } _vendor/rich/layout.py 0000644 00000033340 15030420735 0011016 0 ustar 00 from abc import ABC, abstractmethod from itertools import islice from operator import itemgetter from threading import RLock from typing import ( TYPE_CHECKING, Dict, Iterable, List, NamedTuple, Optional, Sequence, Tuple, Union, ) from ._ratio import ratio_resolve from .align import Align from .console import Console, ConsoleOptions, RenderableType, RenderResult from .highlighter import ReprHighlighter from .panel import Panel from .pretty import Pretty from .repr import rich_repr, Result from .region import Region from .segment import Segment from .style import StyleType if TYPE_CHECKING: from pip._vendor.rich.tree import Tree class LayoutRender(NamedTuple): """An individual layout render.""" region: Region render: List[List[Segment]] RegionMap = Dict["Layout", Region] RenderMap = Dict["Layout", LayoutRender] class LayoutError(Exception): """Layout related error.""" class NoSplitter(LayoutError): """Requested splitter does not exist.""" class _Placeholder: """An internal renderable used as a Layout placeholder.""" highlighter = ReprHighlighter() def __init__(self, layout: "Layout", style: StyleType = "") -> None: self.layout = layout self.style = style def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: width = options.max_width height = options.height or options.size.height layout = self.layout title = ( f"{layout.name!r} ({width} x {height})" if layout.name else f"({width} x {height})" ) yield Panel( Align.center(Pretty(layout), vertical="middle"), style=self.style, title=self.highlighter(title), border_style="blue", ) class Splitter(ABC): """Base class for a splitter.""" name: str = "" @abstractmethod def get_tree_icon(self) -> str: """Get the icon (emoji) used in layout.tree""" @abstractmethod def divide( self, children: Sequence["Layout"], region: Region ) -> Iterable[Tuple["Layout", Region]]: """Divide a region amongst several child layouts. Args: children (Sequence(Layout)): A number of child layouts. region (Region): A rectangular region to divide. """ class RowSplitter(Splitter): """Split a layout region in to rows.""" name = "row" def get_tree_icon(self) -> str: return "[layout.tree.row]⬌" def divide( self, children: Sequence["Layout"], region: Region ) -> Iterable[Tuple["Layout", Region]]: x, y, width, height = region render_widths = ratio_resolve(width, children) offset = 0 _Region = Region for child, child_width in zip(children, render_widths): yield child, _Region(x + offset, y, child_width, height) offset += child_width class ColumnSplitter(Splitter): """Split a layout region in to columns.""" name = "column" def get_tree_icon(self) -> str: return "[layout.tree.column]⬍" def divide( self, children: Sequence["Layout"], region: Region ) -> Iterable[Tuple["Layout", Region]]: x, y, width, height = region render_heights = ratio_resolve(height, children) offset = 0 _Region = Region for child, child_height in zip(children, render_heights): yield child, _Region(x, y + offset, width, child_height) offset += child_height @rich_repr class Layout: """A renderable to divide a fixed height in to rows or columns. Args: renderable (RenderableType, optional): Renderable content, or None for placeholder. Defaults to None. name (str, optional): Optional identifier for Layout. Defaults to None. size (int, optional): Optional fixed size of layout. Defaults to None. minimum_size (int, optional): Minimum size of layout. Defaults to 1. ratio (int, optional): Optional ratio for flexible layout. Defaults to 1. visible (bool, optional): Visibility of layout. Defaults to True. """ splitters = {"row": RowSplitter, "column": ColumnSplitter} def __init__( self, renderable: Optional[RenderableType] = None, *, name: Optional[str] = None, size: Optional[int] = None, minimum_size: int = 1, ratio: int = 1, visible: bool = True, height: Optional[int] = None, ) -> None: self._renderable = renderable or _Placeholder(self) self.size = size self.minimum_size = minimum_size self.ratio = ratio self.name = name self.visible = visible self.height = height self.splitter: Splitter = self.splitters["column"]() self._children: List[Layout] = [] self._render_map: RenderMap = {} self._lock = RLock() def __rich_repr__(self) -> Result: yield "name", self.name, None yield "size", self.size, None yield "minimum_size", self.minimum_size, 1 yield "ratio", self.ratio, 1 @property def renderable(self) -> RenderableType: """Layout renderable.""" return self if self._children else self._renderable @property def children(self) -> List["Layout"]: """Gets (visible) layout children.""" return [child for child in self._children if child.visible] @property def map(self) -> RenderMap: """Get a map of the last render.""" return self._render_map def get(self, name: str) -> Optional["Layout"]: """Get a named layout, or None if it doesn't exist. Args: name (str): Name of layout. Returns: Optional[Layout]: Layout instance or None if no layout was found. """ if self.name == name: return self else: for child in self._children: named_layout = child.get(name) if named_layout is not None: return named_layout return None def __getitem__(self, name: str) -> "Layout": layout = self.get(name) if layout is None: raise KeyError(f"No layout with name {name!r}") return layout @property def tree(self) -> "Tree": """Get a tree renderable to show layout structure.""" from pip._vendor.rich.styled import Styled from pip._vendor.rich.table import Table from pip._vendor.rich.tree import Tree def summary(layout: "Layout") -> Table: icon = layout.splitter.get_tree_icon() table = Table.grid(padding=(0, 1, 0, 0)) text: RenderableType = ( Pretty(layout) if layout.visible else Styled(Pretty(layout), "dim") ) table.add_row(icon, text) _summary = table return _summary layout = self tree = Tree( summary(layout), guide_style=f"layout.tree.{layout.splitter.name}", highlight=True, ) def recurse(tree: "Tree", layout: "Layout") -> None: for child in layout._children: recurse( tree.add( summary(child), guide_style=f"layout.tree.{child.splitter.name}", ), child, ) recurse(tree, self) return tree def split( self, *layouts: Union["Layout", RenderableType], splitter: Union[Splitter, str] = "column", ) -> None: """Split the layout in to multiple sub-layouts. Args: *layouts (Layout): Positional arguments should be (sub) Layout instances. splitter (Union[Splitter, str]): Splitter instance or name of splitter. """ _layouts = [ layout if isinstance(layout, Layout) else Layout(layout) for layout in layouts ] try: self.splitter = ( splitter if isinstance(splitter, Splitter) else self.splitters[splitter]() ) except KeyError: raise NoSplitter(f"No splitter called {splitter!r}") self._children[:] = _layouts def add_split(self, *layouts: Union["Layout", RenderableType]) -> None: """Add a new layout(s) to existing split. Args: *layouts (Union[Layout, RenderableType]): Positional arguments should be renderables or (sub) Layout instances. """ _layouts = ( layout if isinstance(layout, Layout) else Layout(layout) for layout in layouts ) self._children.extend(_layouts) def split_row(self, *layouts: Union["Layout", RenderableType]) -> None: """Split the layout in tow a row (Layouts side by side). Args: *layouts (Layout): Positional arguments should be (sub) Layout instances. """ self.split(*layouts, splitter="row") def split_column(self, *layouts: Union["Layout", RenderableType]) -> None: """Split the layout in to a column (layouts stacked on top of each other). Args: *layouts (Layout): Positional arguments should be (sub) Layout instances. """ self.split(*layouts, splitter="column") def unsplit(self) -> None: """Reset splits to initial state.""" del self._children[:] def update(self, renderable: RenderableType) -> None: """Update renderable. Args: renderable (RenderableType): New renderable object. """ with self._lock: self._renderable = renderable def refresh_screen(self, console: "Console", layout_name: str) -> None: """Refresh a sub-layout. Args: console (Console): Console instance where Layout is to be rendered. layout_name (str): Name of layout. """ with self._lock: layout = self[layout_name] region, _lines = self._render_map[layout] (x, y, width, height) = region lines = console.render_lines( layout, console.options.update_dimensions(width, height) ) self._render_map[layout] = LayoutRender(region, lines) console.update_screen_lines(lines, x, y) def _make_region_map(self, width: int, height: int) -> RegionMap: """Create a dict that maps layout on to Region.""" stack: List[Tuple[Layout, Region]] = [(self, Region(0, 0, width, height))] push = stack.append pop = stack.pop layout_regions: List[Tuple[Layout, Region]] = [] append_layout_region = layout_regions.append while stack: append_layout_region(pop()) layout, region = layout_regions[-1] children = layout.children if children: for child_and_region in layout.splitter.divide(children, region): push(child_and_region) region_map = { layout: region for layout, region in sorted(layout_regions, key=itemgetter(1)) } return region_map def render(self, console: Console, options: ConsoleOptions) -> RenderMap: """Render the sub_layouts. Args: console (Console): Console instance. options (ConsoleOptions): Console options. Returns: RenderMap: A dict that maps Layout on to a tuple of Region, lines """ render_width = options.max_width render_height = options.height or console.height region_map = self._make_region_map(render_width, render_height) layout_regions = [ (layout, region) for layout, region in region_map.items() if not layout.children ] render_map: Dict["Layout", "LayoutRender"] = {} render_lines = console.render_lines update_dimensions = options.update_dimensions for layout, region in layout_regions: lines = render_lines( layout.renderable, update_dimensions(region.width, region.height) ) render_map[layout] = LayoutRender(region, lines) return render_map def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: with self._lock: width = options.max_width or console.width height = options.height or console.height render_map = self.render(console, options.update_dimensions(width, height)) self._render_map = render_map layout_lines: List[List[Segment]] = [[] for _ in range(height)] _islice = islice for (region, lines) in render_map.values(): _x, y, _layout_width, layout_height = region for row, line in zip( _islice(layout_lines, y, y + layout_height), lines ): row.extend(line) new_line = Segment.line() for layout_row in layout_lines: yield from layout_row yield new_line if __name__ == "__main__": from pip._vendor.rich.console import Console console = Console() layout = Layout() layout.split_column( Layout(name="header", size=3), Layout(ratio=1, name="main"), Layout(size=10, name="footer"), ) layout["main"].split_row(Layout(name="side"), Layout(name="body", ratio=2)) layout["body"].split_row(Layout(name="content", ratio=2), Layout(name="s2")) layout["s2"].split_column( Layout(name="top"), Layout(name="middle"), Layout(name="bottom") ) layout["side"].split_column(Layout(layout.tree, name="left1"), Layout(name="left2")) layout["content"].update("foo") console.print(layout) _vendor/rich/status.py 0000644 00000010511 15030420735 0011017 0 ustar 00 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") _vendor/rich/__pycache__/protocol.cpython-310.pyc 0000644 00000002467 15030420735 0015707 0 ustar 00 o �7]hy � @ sd d dl mZmZmZmZmZ d dlmZ erd dlm Z dZ dedefdd�Zd e dd fdd�Zd S )� )�Any�Callable�cast�Set� TYPE_CHECKING)�isclass��RenderableType�-aihwerij235234ljsdnp34ksodfipwoe234234jlskjdf�check_object�returnc C s t | t�pt| d�pt| d�S )z+Check if an object may be rendered by Rich.�__rich__�__rich_console__)� isinstance�str�hasattr)r � r �;/usr/lib/python3/dist-packages/pip/_vendor/rich/protocol.py� is_renderable s ��r � renderabler c C s~ ddl m} t� }t| d�r:t| �s:t| t�rt| �S t| d�}|� } t| �}||v r,n|� |� t| d�r:t| �rt || �S )z�Cast an object to a renderable by calling __rich__ if present. Args: renderable (object): A potentially renderable object Returns: object: The result of recursively calling __rich__. r r r )�pip._vendor.rich.consoler �setr r � _GIBBERISH�repr�getattr�type�addr )r r �rich_visited_set�cast_method�renderable_typer r r � rich_cast s � r N)�typingr r r r r �inspectr r r r �boolr �objectr r r r r �<module> s _vendor/rich/__pycache__/jupyter.cpython-310.pyc 0000644 00000007316 15030420735 0015546 0 ustar 00 o �7]h� � @ s� d dl mZmZmZmZ ddlmZ ddlmZ ddl m Z dZG dd� d�ZG d d � d �Z dee defd d�Zdee deddfdd�Zdededdfdd�ZdS )� )�Any�Dict�Iterable�List� )�get_console)�Segment)�DEFAULT_TERMINAL_THEMEz�<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre> c @ sP e Zd ZdZdededdfdd�Zdee d ee d edeeef fdd�Z dS ) �JupyterRenderablez)A shim to write html to Jupyter notebook.�html�text�returnNc C s || _ || _d S )N)r r )�selfr r � r �:/usr/lib/python3/dist-packages/pip/_vendor/rich/jupyter.py�__init__ s zJupyterRenderable.__init__�include�exclude�kwargsc sF | j | jd�}�r�fdd�|�� D �}� r!� fdd�|�� D �}|S )N�z text/plainz text/htmlc � i | ]\}}|� v r||�qS r r ��.0�k�v�r r r � <dictcomp> � z7JupyterRenderable._repr_mimebundle_.<locals>.<dictcomp>c � i | ]\}}|� vr||�qS r r r �r r r r r )r r �items)r r r r �datar �r r r �_repr_mimebundle_ s z#JupyterRenderable._repr_mimebundle_) �__name__� __module__�__qualname__�__doc__�strr r r r r# r r r r r s ��� �r c @ s>