File manager - Edit - /home/newsbmcs.com/public_html/static/img/logo/_vendor.tar
Back
rich/_palettes.py 0000644 00000015627 15030104210 0010020 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), ] ) rich/cells.py 0000644 00000012012 15030104210 0007123 0 ustar 00 from __future__ import annotations from functools import lru_cache from typing import Callable from ._cell_widths import CELL_WIDTHS # Ranges of unicode ordinals that produce a 1-cell wide character # This is non-exhaustive, but covers most common Western characters _SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [ (0x20, 0x7E), # Latin (excluding non-printable) (0xA0, 0xAC), (0xAE, 0x002FF), (0x00370, 0x00482), # Greek / Cyrillic (0x02500, 0x025FC), # Box drawing, box elements, geometric shapes (0x02800, 0x028FF), # Braille ] # A set of characters that are a single cell wide _SINGLE_CELLS = frozenset( [ character for _start, _end in _SINGLE_CELL_UNICODE_RANGES for character in map(chr, range(_start, _end + 1)) ] ) # When called with a string this will return True if all # characters are single-cell, otherwise False _is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset @lru_cache(4096) def cached_cell_len(text: str) -> int: """Get the number of cells required to display text. This method always caches, which may use up a lot of memory. It is recommended to use `cell_len` over this method. Args: text (str): Text to display. Returns: int: Get the number of cells required to display text. """ if _is_single_cell_widths(text): return len(text) return sum(map(get_character_cell_size, text)) def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int: """Get the number of cells required to display text. Args: text (str): Text to display. Returns: int: Get the number of cells required to display text. """ if len(text) < 512: return _cell_len(text) if _is_single_cell_widths(text): return len(text) return sum(map(get_character_cell_size, text)) @lru_cache(maxsize=4096) def get_character_cell_size(character: str) -> int: """Get the cell size of a character. Args: character (str): A single character. Returns: int: Number of cells (0, 1 or 2) occupied by that character. """ codepoint = ord(character) _table = CELL_WIDTHS lower_bound = 0 upper_bound = len(_table) - 1 index = (lower_bound + upper_bound) // 2 while True: start, end, width = _table[index] if codepoint < start: upper_bound = index - 1 elif codepoint > end: lower_bound = index + 1 else: return 0 if width == -1 else width if upper_bound < lower_bound: break index = (lower_bound + upper_bound) // 2 return 1 def set_cell_size(text: str, total: int) -> str: """Set the length of a string to fit within given number of cells.""" if _is_single_cell_widths(text): size = len(text) if size < total: return text + " " * (total - size) return text[:total] if total <= 0: return "" cell_size = cell_len(text) if cell_size == total: return text if cell_size < total: return text + " " * (total - cell_size) start = 0 end = len(text) # Binary search until we find the right size while True: pos = (start + end) // 2 before = text[: pos + 1] before_len = cell_len(before) if before_len == total + 1 and cell_len(before[-1]) == 2: return before[:-1] + " " if before_len == total: return before if before_len > total: end = pos else: start = pos def chop_cells( text: str, width: int, ) -> list[str]: """Split text into lines such that each line fits within the available (cell) width. Args: text: The text to fold such that it fits in the given width. width: The width available (number of cells). Returns: A list of strings such that each string in the list has cell width less than or equal to the available width. """ _get_character_cell_size = get_character_cell_size lines: list[list[str]] = [[]] append_new_line = lines.append append_to_last_line = lines[-1].append total_width = 0 for character in text: cell_width = _get_character_cell_size(character) char_doesnt_fit = total_width + cell_width > width if char_doesnt_fit: append_new_line([character]) append_to_last_line = lines[-1].append total_width = cell_width else: append_to_last_line(character) total_width += cell_width return ["".join(line) for line in lines] if __name__ == "__main__": # pragma: no cover print(get_character_cell_size("😽")) for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8): print(line) for n in range(80, 1, -1): print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|") print("x" * n) rich/rule.py 0000644 00000010772 15030104210 0007003 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 .measure import Measurement 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 characters = ( "-" if (options.ascii_only and not self.characters.isascii()) else self.characters ) chars_len = cell_len(characters) if not self.title: yield self._rule_line(chars_len, width) 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() required_space = 4 if self.align == "center" else 2 truncate_width = max(0, width - required_space) if not truncate_width: yield self._rule_line(chars_len, width) return rule_text = Text(end=self.end) if self.align == "center": title_text.truncate(truncate_width, 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(truncate_width, 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(truncate_width, 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 def _rule_line(self, chars_len: int, width: int) -> Text: rule_text = Text(self.characters * ((width // chars_len) + 1), self.style) rule_text.truncate(width) rule_text.plain = set_cell_size(rule_text.plain, width) return rule_text def __rich_measure__( self, console: Console, options: ConsoleOptions ) -> Measurement: return Measurement(1, 1) if __name__ == "__main__": # pragma: no cover import sys from pip._vendor.rich.console import Console try: text = sys.argv[1] except IndexError: text = "Hello, World" console = Console() console.print(Rule(title=text)) console = Console() console.print(Rule("foo"), width=4) rich/box.py 0000644 00000025117 15030104210 0006623 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_plain_headed_box(self) -> "Box": """If this box uses special characters for the borders of the header, then return the equivalent box that does not. Returns: Box: The most similar Box that doesn't use header-specific box characters. If the current Box already satisfies this criterion, then it's returned. """ return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) def get_top(self, widths: Iterable[int]) -> str: """Get the top of a simple box. Args: widths (List[int]): Widths of columns. Returns: str: A string of box characters. """ parts: List[str] = [] append = parts.append append(self.top_left) for last, width in loop_last(widths): append(self.top * width) if not last: append(self.top_divider) append(self.top_right) return "".join(parts) def get_row( self, widths: Iterable[int], level: Literal["head", "row", "foot", "mid"] = "row", edge: bool = True, ) -> str: """Get the top of a simple box. Args: width (List[int]): Widths of columns. Returns: str: A string of box characters. """ if level == "head": left = self.head_row_left horizontal = self.head_row_horizontal cross = self.head_row_cross right = self.head_row_right elif level == "row": left = self.row_left horizontal = self.row_horizontal cross = self.row_cross right = self.row_right elif level == "mid": left = self.mid_left horizontal = " " cross = self.mid_vertical right = self.mid_right elif level == "foot": left = self.foot_row_left horizontal = self.foot_row_horizontal cross = self.foot_row_cross right = self.foot_row_right else: raise ValueError("level must be 'head', 'row' or 'foot'") parts: List[str] = [] append = parts.append if edge: append(left) for last, width in loop_last(widths): append(horizontal * width) if not last: append(cross) if edge: append(right) return "".join(parts) def get_bottom(self, widths: Iterable[int]) -> str: """Get the bottom of a simple box. Args: widths (List[int]): Widths of columns. Returns: str: A string of box characters. """ parts: List[str] = [] append = parts.append append(self.bottom_left) for last, width in loop_last(widths): append(self.bottom * width) if not last: append(self.bottom_divider) append(self.bottom_right) return "".join(parts) # fmt: off ASCII: Box = Box( "+--+\n" "| ||\n" "|-+|\n" "| ||\n" "|-+|\n" "|-+|\n" "| ||\n" "+--+\n", ascii=True, ) ASCII2: Box = Box( "+-++\n" "| ||\n" "+-++\n" "| ||\n" "+-++\n" "+-++\n" "| ||\n" "+-++\n", ascii=True, ) ASCII_DOUBLE_HEAD: Box = Box( "+-++\n" "| ||\n" "+=++\n" "| ||\n" "+-++\n" "+-++\n" "| ||\n" "+-++\n", ascii=True, ) SQUARE: Box = Box( "┌─┬┐\n" "│ ││\n" "├─┼┤\n" "│ ││\n" "├─┼┤\n" "├─┼┤\n" "│ ││\n" "└─┴┘\n" ) SQUARE_DOUBLE_HEAD: Box = Box( "┌─┬┐\n" "│ ││\n" "╞═╪╡\n" "│ ││\n" "├─┼┤\n" "├─┼┤\n" "│ ││\n" "└─┴┘\n" ) MINIMAL: Box = Box( " ╷ \n" " │ \n" "╶─┼╴\n" " │ \n" "╶─┼╴\n" "╶─┼╴\n" " │ \n" " ╵ \n" ) MINIMAL_HEAVY_HEAD: Box = Box( " ╷ \n" " │ \n" "╺━┿╸\n" " │ \n" "╶─┼╴\n" "╶─┼╴\n" " │ \n" " ╵ \n" ) MINIMAL_DOUBLE_HEAD: Box = Box( " ╷ \n" " │ \n" " ═╪ \n" " │ \n" " ─┼ \n" " ─┼ \n" " │ \n" " ╵ \n" ) SIMPLE: Box = Box( " \n" " \n" " ── \n" " \n" " \n" " ── \n" " \n" " \n" ) SIMPLE_HEAD: Box = Box( " \n" " \n" " ── \n" " \n" " \n" " \n" " \n" " \n" ) SIMPLE_HEAVY: Box = Box( " \n" " \n" " ━━ \n" " \n" " \n" " ━━ \n" " \n" " \n" ) HORIZONTALS: Box = Box( " ── \n" " \n" " ── \n" " \n" " ── \n" " ── \n" " \n" " ── \n" ) ROUNDED: Box = Box( "╭─┬╮\n" "│ ││\n" "├─┼┤\n" "│ ││\n" "├─┼┤\n" "├─┼┤\n" "│ ││\n" "╰─┴╯\n" ) HEAVY: Box = Box( "┏━┳┓\n" "┃ ┃┃\n" "┣━╋┫\n" "┃ ┃┃\n" "┣━╋┫\n" "┣━╋┫\n" "┃ ┃┃\n" "┗━┻┛\n" ) HEAVY_EDGE: Box = Box( "┏━┯┓\n" "┃ │┃\n" "┠─┼┨\n" "┃ │┃\n" "┠─┼┨\n" "┠─┼┨\n" "┃ │┃\n" "┗━┷┛\n" ) HEAVY_HEAD: Box = Box( "┏━┳┓\n" "┃ ┃┃\n" "┡━╇┩\n" "│ ││\n" "├─┼┤\n" "├─┼┤\n" "│ ││\n" "└─┴┘\n" ) DOUBLE: Box = Box( "╔═╦╗\n" "║ ║║\n" "╠═╬╣\n" "║ ║║\n" "╠═╬╣\n" "╠═╬╣\n" "║ ║║\n" "╚═╩╝\n" ) DOUBLE_EDGE: Box = Box( "╔═╤╗\n" "║ │║\n" "╟─┼╢\n" "║ │║\n" "╟─┼╢\n" "╟─┼╢\n" "║ │║\n" "╚═╧╝\n" ) MARKDOWN: Box = Box( " \n" "| ||\n" "|-||\n" "| ||\n" "|-||\n" "|-||\n" "| ||\n" " \n", ascii=True, ) # fmt: on # Map Boxes that don't render with raster fonts on to equivalent that do LEGACY_WINDOWS_SUBSTITUTIONS = { ROUNDED: SQUARE, MINIMAL_HEAVY_HEAD: MINIMAL, SIMPLE_HEAVY: SIMPLE, HEAVY: SQUARE, HEAVY_EDGE: SQUARE, HEAVY_HEAD: SQUARE, } # Map headed boxes to their headerless equivalents PLAIN_HEADED_SUBSTITUTIONS = { HEAVY_HEAD: SQUARE, SQUARE_DOUBLE_HEAD: SQUARE, MINIMAL_DOUBLE_HEAD: MINIMAL, MINIMAL_HEAVY_HEAD: MINIMAL, ASCII_DOUBLE_HEAD: ASCII2, } if __name__ == "__main__": # pragma: no cover from pip._vendor.rich.columns import Columns from pip._vendor.rich.panel import Panel from . import box as box from .console import Console from .table import Table from .text import Text console = Console(record=True) BOXES = [ "ASCII", "ASCII2", "ASCII_DOUBLE_HEAD", "SQUARE", "SQUARE_DOUBLE_HEAD", "MINIMAL", "MINIMAL_HEAVY_HEAD", "MINIMAL_DOUBLE_HEAD", "SIMPLE", "SIMPLE_HEAD", "SIMPLE_HEAVY", "HORIZONTALS", "ROUNDED", "HEAVY", "HEAVY_EDGE", "HEAVY_HEAD", "DOUBLE", "DOUBLE_EDGE", "MARKDOWN", ] console.print(Panel("[bold green]Box Constants", style="green"), justify="center") console.print() columns = Columns(expand=True, padding=2) for box_name in sorted(BOXES): table = Table( show_footer=True, style="dim", border_style="not dim", expand=True ) table.add_column("Header 1", "Footer 1") table.add_column("Header 2", "Footer 2") table.add_row("Cell", "Cell") table.add_row("Cell", "Cell") table.box = getattr(box, box_name) table.title = Text(f"box.{box_name}", style="magenta") columns.add_renderable(table) console.print(columns) # console.save_svg("box.svg") rich/diagnose.py 0000644 00000001714 15030104210 0007621 0 ustar 00 import os import platform from pip._vendor.rich import inspect from pip._vendor.rich.console import Console, get_windows_console_features from pip._vendor.rich.panel import Panel from pip._vendor.rich.pretty import Pretty def report() -> None: # pragma: no cover """Print a report to the terminal with debugging information""" console = Console() inspect(console) features = get_windows_console_features() inspect(features) env_names = ( "TERM", "COLORTERM", "CLICOLOR", "NO_COLOR", "TERM_PROGRAM", "COLUMNS", "LINES", "JUPYTER_COLUMNS", "JUPYTER_LINES", "JPY_PARENT_PID", "VSCODE_VERBOSE_LOGGING", ) env = {name: os.getenv(name) for name in env_names} console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables")) console.print(f'platform="{platform.system()}"') if __name__ == "__main__": # pragma: no cover report() rich/text.py 0000644 00000134700 15030104210 0007016 0 ustar 00 import re from functools import partial, reduce from math import gcd from operator import itemgetter from typing import ( TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Optional, Pattern, 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"] """A plain string or a :class:`Text` instance.""" 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})" 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) def extend(self, cells: int) -> "Span": """Extend the span by the given number of cells. Args: cells (int): Additional space to add to end of span. Returns: Span: A span. """ if cells: start, end, style = self return Span(start, end + cells, style) else: return self 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 None. 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] = None, spans: Optional[List[Span]] = None, ) -> None: sanitized_text = strip_control_codes(text) self._text = [sanitized_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(sanitized_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} {self.style!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, end: str = "\n", ) -> "Text": """Create Text instance from markup. Args: text (str): A string containing console markup. style (Union[str, Style], optional): Base style for text. Defaults to "". emoji (bool, optional): Also render emoji code. Defaults to True. emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. 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". 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 rendered_text.end = end 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 None. """ 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. 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 None. 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: sanitized_text = strip_control_codes(new_text) self._text[:] = [sanitized_text] old_length = self._length self._length = len(sanitized_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 metadata (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 stylize_before( 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. Styles will be applied before other styles already present. 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.insert(0, Span(start, min(length, end), style)) def apply_meta( self, meta: Dict[str, Any], start: int = 0, end: Optional[int] = None ) -> None: """Apply metadata 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 extend_style(self, spaces: int) -> None: """Extend the Text given number of spaces where the spaces have the same style as the last character. Args: spaces (int): Number of spaces to add to the Text. """ if spaces <= 0: return spans = self.spans new_spaces = " " * spaces if spans: end_offset = len(self) self._spans[:] = [ span.extend(spaces) if span.end >= end_offset else span for span in spans ] self._text.append(new_spaces) self._length += spaces else: self.plain += new_spaces def highlight_regex( self, re_highlight: Union[Pattern[str], 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 (Union[re.Pattern, str]): A regular expression object or string. 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 if isinstance(re_highlight, str): re_highlight = re.compile(re_highlight) for match in re_highlight.finditer(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]): Words to highlight. style (Union[str, Style]): Style to apply. case_sensitive (bool, optional): Enable case sensitive matching. 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 if self.tab_size is None else self.tab_size 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 if tab_size is None: tab_size = self.tab_size if tab_size is None: tab_size = 8 new_text: List[Text] = [] append = new_text.append for line in self.split("\n", include_separator=True): if "\t" not in line.plain: append(line) else: cell_position = 0 parts = line.split("\t", include_separator=True) for part in parts: if part.plain.endswith("\t"): part._text[-1] = part._text[-1][:-1] + " " cell_position += part.cell_len tab_remainder = cell_position % tab_size if tab_remainder: spaces = tab_size - tab_remainder part.extend_style(spaces) cell_position += spaces else: cell_position += part.cell_len append(part) result = Text("").join(new_text) 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. character (str): The character to pad with. Must be a string of length 1. """ 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): sanitized_text = strip_control_codes(text) self._text.append(sanitized_text) offset = len(self) text_length = len(sanitized_text) if style: 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: 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.copy() ) 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. Args: text (Text): The Text instance to append to this instance. Returns: Text: Returns self for chaining. """ _Span = Span text_length = self._length if text.style: 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.copy() ) 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: tokens (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: content = strip_control_codes(content) append_text(content) if style: 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 cells available per line. 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: Lines container. """ 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() rich/traceback.py 0000644 00000076065 15030104210 0007762 0 ustar 00 import inspect import linecache import os import sys from dataclasses import dataclass, field from itertools import islice from traceback import walk_tb from types import ModuleType, TracebackType from typing import ( Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, 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 pip._vendor.pygments.util import ClassNotFound from . import pretty from ._loop import 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 = sys.platform == "win32" LOCALS_MAX_LENGTH = 10 LOCALS_MAX_STRING = 80 def install( *, console: Optional[Console] = None, width: Optional[int] = 100, code_width: Optional[int] = 88, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, locals_hide_dunder: bool = True, locals_hide_sunder: Optional[bool] = None, 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. code_width (Optional[int], optional): Code width (in characters) of traceback. Defaults to 88. 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. 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. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. 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(stderr=True) if console is None else console locals_hide_sunder = ( True if (traceback_console.is_jupyter and locals_hide_sunder is None) else locals_hide_sunder ) def excepthook( type_: Type[BaseException], value: BaseException, traceback: Optional[TracebackType], ) -> None: traceback_console.print( Traceback.from_exception( type_, value, traceback, width=width, code_width=code_width, extra_lines=extra_lines, theme=theme, word_wrap=word_wrap, show_locals=show_locals, locals_max_length=locals_max_length, locals_max_string=locals_max_string, locals_hide_dunder=locals_hide_dunder, locals_hide_sunder=bool(locals_hide_sunder), 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[name-defined] 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 last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] = 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. code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88. 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. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. 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, code_width: Optional[int] = 88, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, locals_hide_dunder: bool = True, locals_hide_sunder: bool = False, indent_guides: bool = True, 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.code_width = code_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.locals_hide_dunder = locals_hide_dunder self.locals_hide_sunder = locals_hide_sunder 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, code_width: Optional[int] = 88, extra_lines: int = 3, theme: Optional[str] = None, word_wrap: bool = False, show_locals: bool = False, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, locals_hide_dunder: bool = True, locals_hide_sunder: bool = False, indent_guides: bool = True, 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. code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88. 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. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. 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, locals_max_length=locals_max_length, locals_max_string=locals_max_string, locals_hide_dunder=locals_hide_dunder, locals_hide_sunder=locals_hide_sunder, ) return cls( rich_traceback, width=width, code_width=code_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, locals_hide_dunder=locals_hide_dunder, locals_hide_sunder=locals_hide_sunder, 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, locals_hide_dunder: bool = True, locals_hide_sunder: bool = False, ) -> 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. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False. 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 propagate.""" 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 def get_locals( iter_locals: Iterable[Tuple[str, object]] ) -> Iterable[Tuple[str, object]]: """Extract locals from an iterator of key pairs.""" if not (locals_hide_dunder or locals_hide_sunder): yield from iter_locals return for key, value in iter_locals: if locals_hide_dunder and key.startswith("__"): continue if locals_hide_sunder and key.startswith("_"): continue yield key, value for frame_summary, line_no in walk_tb(traceback): filename = frame_summary.f_code.co_filename last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] last_instruction = None if sys.version_info >= (3, 11): instruction_index = frame_summary.f_lasti // 2 instruction_position = next( islice( frame_summary.f_code.co_positions(), instruction_index, instruction_index + 1, ) ) ( start_line, end_line, start_column, end_column, ) = instruction_position if ( start_line is not None and end_line is not None and start_column is not None and end_column is not None ): last_instruction = ( (start_line, start_column), (end_line, end_column), ) if filename and not filename.startswith("<"): if not os.path.isabs(filename): filename = os.path.join(_IMPORT_CWD, filename) if frame_summary.f_locals.get("_rich_traceback_omit", False): continue 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 get_locals(frame_summary.f_locals.items()) if not (inspect.isfunction(value) or inspect.isclass(value)) } if show_locals else None ), last_instruction=last_instruction, ) append(frame) if frame_summary.f_locals.get("_rich_traceback_guard", False): del stack.frames[:] cause = getattr(exc_value, "__cause__", None) if cause: exc_type = cause.__class__ exc_value = cause # __traceback__ can be None, e.g. for exceptions raised by the # 'multiprocessing' module traceback = cause.__traceback__ is_cause = True continue cause = exc_value.__context__ if cause and not getattr(exc_value, "__suppress_context__", False): exc_type = cause.__class__ exc_value = cause traceback = cause.__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>": if os.path.exists(syntax_error.filename): 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" try: return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name except ClassNotFound: return "text" @group() def _render_stack(self, stack: Stack) -> RenderResult: path_highlighter = PathHighlighter() theme = self.theme def read_code(filename: str) -> str: """Read files, and cache results on filename. Args: filename (str): Filename to read Returns: str: Contents of file """ return "".join(linecache.getlines(filename)) 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 == 0 frame_filename = frame.filename suppressed = any(frame_filename.startswith(path) for path in self.suppress) if os.path.exists(frame.filename): 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", ) else: text = Text.assemble( "in ", (frame.name, "pygments.function"), (":", "pygments.text"), (str(frame.lineno), "pygments.number"), 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) if not code: # code may be an empty string if the file doesn't exist, OR # if the traceback filename is generated dynamically continue 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=self.code_width, indent_guides=self.indent_guides, dedent=False, ) yield "" except Exception as error: yield Text.assemble( (f"\n{error}", "traceback.error"), ) else: if frame.last_instruction is not None: start, end = frame.last_instruction syntax.stylize_range( style="traceback.error_range", start=start, end=end, style_before=True, ) yield ( Columns( [ syntax, *render_locals(frame), ], padding=1, ) if frame.locals else syntax ) if __name__ == "__main__": # pragma: no cover install(show_locals=True) 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: foo(0) error() rich/segment.py 0000644 00000060247 15030104210 0007500 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, cached_cell_len, cell_len, get_character_cell_size, set_cell_size, ) from .repr import Result, rich_repr from .style import Style if TYPE_CHECKING: from .console import Console, ConsoleOptions, RenderResult log = getLogger("rich") class ControlType(IntEnum): """Non-printable control codes which typically translate to ANSI codes.""" BELL = 1 CARRIAGE_RETURN = 2 HOME = 3 CLEAR = 4 SHOW_CURSOR = 5 HIDE_CURSOR = 6 ENABLE_ALT_SCREEN = 7 DISABLE_ALT_SCREEN = 8 CURSOR_UP = 9 CURSOR_DOWN = 10 CURSOR_FORWARD = 11 CURSOR_BACKWARD = 12 CURSOR_MOVE_TO_COLUMN = 13 CURSOR_MOVE_TO = 14 ERASE_IN_LINE = 15 SET_WINDOW_TITLE = 16 ControlCode = Union[ Tuple[ControlType], Tuple[ControlType, Union[int, str]], Tuple[ControlType, int, int], ] @rich_repr() class Segment(NamedTuple): """A piece of text with associated style. Segments are produced by the Console render process and are ultimately converted in to strings to be written to the terminal. Args: text (str): A piece of text. style (:class:`~rich.style.Style`, optional): An optional style to apply to the text. control (Tuple[ControlCode], optional): Optional sequence of control codes. Attributes: cell_length (int): The cell length of this Segment. """ text: str style: Optional[Style] = None control: Optional[Sequence[ControlCode]] = None @property def cell_length(self) -> int: """The number of terminal cells required to display self.text. Returns: int: A number of cells. """ text, _style, control = self return 0 if control else cell_len(text) def __rich_repr__(self) -> Result: yield self.text if self.control is None: if self.style is not None: yield self.style else: yield self.style yield self.control def __bool__(self) -> bool: """Check if the segment contains text.""" return bool(self.text) @property def is_control(self) -> bool: """Check if the segment contains control codes.""" return self.control is not None @classmethod @lru_cache(1024 * 16) def _split_cells(cls, segment: "Segment", cut: int) -> Tuple["Segment", "Segment"]: """Split a segment in to two at a given cell position. Note that splitting a double-width character, may result in that character turning into two spaces. Args: segment (Segment): A segment to split. cut (int): A cell position to cut on. Returns: A tuple of two segments. """ text, style, control = segment _Segment = Segment cell_length = segment.cell_length if cut >= cell_length: return segment, _Segment("", style, control) cell_size = get_character_cell_size pos = int((cut / cell_length) * len(text)) while True: before = text[:pos] cell_pos = cell_len(before) out_by = cell_pos - cut if not out_by: return ( _Segment(before, style, control), _Segment(text[pos:], style, control), ) if out_by == -1 and cell_size(text[pos]) == 2: return ( _Segment(text[:pos] + " ", style, control), _Segment(" " + text[pos + 1 :], style, control), ) if out_by == +1 and cell_size(text[pos - 1]) == 2: return ( _Segment(text[: pos - 1] + " ", style, control), _Segment(" " + text[pos:], style, control), ) if cell_pos < cut: pos += 1 else: pos -= 1 def split_cells(self, cut: int) -> Tuple["Segment", "Segment"]: """Split segment in to two segments at the specified column. If the cut point falls in the middle of a 2-cell wide character then it is replaced by two spaces, to preserve the display width of the parent segment. Args: cut (int): Offset within the segment to cut. Returns: Tuple[Segment, Segment]: Two segments. """ text, style, control = self assert cut >= 0 if _is_single_cell_widths(text): # Fast path with all 1 cell characters if cut >= len(text): return self, Segment("", style, control) return ( Segment(text[:cut], style, control), Segment(text[cut:], style, control), ) return self._split_cells(self, cut) @classmethod def line(cls) -> "Segment": """Make a new line segment.""" return cls("\n") @classmethod def apply_style( cls, segments: Iterable["Segment"], style: Optional[Style] = None, post_style: Optional[Style] = None, ) -> Iterable["Segment"]: """Apply style(s) to an iterable of segments. Returns an iterable of segments where the style is replaced by ``style + segment.style + post_style``. Args: segments (Iterable[Segment]): Segments to process. style (Style, optional): Base style. Defaults to None. post_style (Style, optional): Style to apply on top of segment style. Defaults to None. Returns: Iterable[Segments]: A new iterable of segments (possibly the same iterable). """ result_segments = segments if style: apply = style.__add__ result_segments = ( cls(text, None if control else apply(_style), control) for text, _style, control in result_segments ) if post_style: result_segments = ( cls( text, ( None if control else (_style + post_style if _style else post_style) ), control, ) for text, _style, control in result_segments ) return result_segments @classmethod def filter_control( cls, segments: Iterable["Segment"], is_control: bool = False ) -> Iterable["Segment"]: """Filter segments by ``is_control`` attribute. Args: segments (Iterable[Segment]): An iterable of Segment instances. is_control (bool, optional): is_control flag to match in search. Returns: Iterable[Segment]: And iterable of Segment instances. """ if is_control: return filter(attrgetter("control"), segments) else: return filterfalse(attrgetter("control"), segments) @classmethod def split_lines(cls, segments: Iterable["Segment"]) -> Iterable[List["Segment"]]: """Split a sequence of segments in to a list of lines. Args: segments (Iterable[Segment]): Segments potentially containing line feeds. Yields: Iterable[List[Segment]]: Iterable of segment lists, one per line. """ line: List[Segment] = [] append = line.append for segment in segments: if "\n" in segment.text and not segment.control: text, style, _ = segment while text: _text, new_line, text = text.partition("\n") if _text: append(cls(_text, style)) if new_line: yield line line = [] append = line.append else: append(segment) if line: yield line @classmethod def split_and_crop_lines( cls, segments: Iterable["Segment"], length: int, style: Optional[Style] = None, pad: bool = True, include_new_lines: bool = True, ) -> Iterable[List["Segment"]]: """Split segments in to lines, and crop lines greater than a given length. Args: segments (Iterable[Segment]): An iterable of segments, probably generated from console.render. length (int): Desired line length. style (Style, optional): Style to use for any padding. pad (bool): Enable padding of lines that are less than `length`. Returns: Iterable[List[Segment]]: An iterable of lines of segments. """ line: List[Segment] = [] append = line.append adjust_line_length = cls.adjust_line_length new_line_segment = cls("\n") for segment in segments: if "\n" in segment.text and not segment.control: text, segment_style, _ = segment while text: _text, new_line, text = text.partition("\n") if _text: append(cls(_text, segment_style)) if new_line: cropped_line = adjust_line_length( line, length, style=style, pad=pad ) if include_new_lines: cropped_line.append(new_line_segment) yield cropped_line line.clear() else: append(segment) if line: yield adjust_line_length(line, length, style=style, pad=pad) @classmethod def adjust_line_length( cls, line: List["Segment"], length: int, style: Optional[Style] = None, pad: bool = True, ) -> List["Segment"]: """Adjust a line to a given width (cropping or padding as required). Args: segments (Iterable[Segment]): A list of segments in a single line. length (int): The desired width of the line. style (Style, optional): The style of padding if used (space on the end). Defaults to None. pad (bool, optional): Pad lines with spaces if they are shorter than `length`. Defaults to True. Returns: List[Segment]: A line of segments with the desired length. """ line_length = sum(segment.cell_length for segment in line) new_line: List[Segment] if line_length < length: if pad: new_line = line + [cls(" " * (length - line_length), style)] else: new_line = line[:] elif line_length > length: new_line = [] append = new_line.append line_length = 0 for segment in line: segment_length = segment.cell_length if line_length + segment_length < length or segment.control: append(segment) line_length += segment_length else: text, segment_style, _ = segment text = set_cell_size(text, length - line_length) append(cls(text, segment_style)) break else: new_line = line[:] return new_line @classmethod def get_line_length(cls, line: List["Segment"]) -> int: """Get the length of list of segments. Args: line (List[Segment]): A line encoded as a list of Segments (assumes no '\\\\n' characters), Returns: int: The length of the line. """ _cell_len = cell_len return sum(_cell_len(text) for text, style, control in line if not control) @classmethod def get_shape(cls, lines: List[List["Segment"]]) -> Tuple[int, int]: """Get the shape (enclosing rectangle) of a list of lines. Args: lines (List[List[Segment]]): A list of lines (no '\\\\n' characters). Returns: Tuple[int, int]: Width and height in characters. """ get_line_length = cls.get_line_length max_width = max(get_line_length(line) for line in lines) if lines else 0 return (max_width, len(lines)) @classmethod def set_shape( cls, lines: List[List["Segment"]], width: int, height: Optional[int] = None, style: Optional[Style] = None, new_lines: bool = False, ) -> List[List["Segment"]]: """Set the shape of a list of lines (enclosing rectangle). Args: lines (List[List[Segment]]): A list of lines. width (int): Desired width. height (int, optional): Desired height or None for no change. style (Style, optional): Style of any padding added. new_lines (bool, optional): Padded lines should include "\n". Defaults to False. Returns: List[List[Segment]]: New list of lines. """ _height = height or len(lines) blank = ( [cls(" " * width + "\n", style)] if new_lines else [cls(" " * width, style)] ) adjust_line_length = cls.adjust_line_length shaped_lines = lines[:_height] shaped_lines[:] = [ adjust_line_length(line, width, style=style) for line in lines ] if len(shaped_lines) < _height: shaped_lines.extend([blank] * (_height - len(shaped_lines))) return shaped_lines @classmethod def align_top( cls: Type["Segment"], lines: List[List["Segment"]], width: int, height: int, style: Style, new_lines: bool = False, ) -> List[List["Segment"]]: """Aligns lines to top (adds extra lines to bottom as required). Args: lines (List[List[Segment]]): A list of lines. width (int): Desired width. height (int, optional): Desired height or None for no change. style (Style): Style of any padding added. new_lines (bool, optional): Padded lines should include "\n". Defaults to False. Returns: List[List[Segment]]: New list of lines. """ extra_lines = height - len(lines) if not extra_lines: return lines[:] lines = lines[:height] blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) lines = lines + [[blank]] * extra_lines return lines @classmethod def align_bottom( cls: Type["Segment"], lines: List[List["Segment"]], width: int, height: int, style: Style, new_lines: bool = False, ) -> List[List["Segment"]]: """Aligns render to bottom (adds extra lines above as required). Args: lines (List[List[Segment]]): A list of lines. width (int): Desired width. height (int, optional): Desired height or None for no change. style (Style): Style of any padding added. Defaults to None. new_lines (bool, optional): Padded lines should include "\n". Defaults to False. Returns: List[List[Segment]]: New list of lines. """ extra_lines = height - len(lines) if not extra_lines: return lines[:] lines = lines[:height] blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) lines = [[blank]] * extra_lines + lines return lines @classmethod def align_middle( cls: Type["Segment"], lines: List[List["Segment"]], width: int, height: int, style: Style, new_lines: bool = False, ) -> List[List["Segment"]]: """Aligns lines to middle (adds extra lines to above and below as required). Args: lines (List[List[Segment]]): A list of lines. width (int): Desired width. height (int, optional): Desired height or None for no change. style (Style): Style of any padding added. new_lines (bool, optional): Padded lines should include "\n". Defaults to False. Returns: List[List[Segment]]: New list of lines. """ extra_lines = height - len(lines) if not extra_lines: return lines[:] lines = lines[:height] blank = cls(" " * width + "\n", style) if new_lines else cls(" " * width, style) top_lines = extra_lines // 2 bottom_lines = extra_lines - top_lines lines = [[blank]] * top_lines + lines + [[blank]] * bottom_lines return lines @classmethod def simplify(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: """Simplify an iterable of segments by combining contiguous segments with the same style. Args: segments (Iterable[Segment]): An iterable of segments. Returns: Iterable[Segment]: A possibly smaller iterable of segments that will render the same way. """ iter_segments = iter(segments) try: last_segment = next(iter_segments) except StopIteration: return _Segment = Segment for segment in iter_segments: if last_segment.style == segment.style and not segment.control: last_segment = _Segment( last_segment.text + segment.text, last_segment.style ) else: yield last_segment last_segment = segment yield last_segment @classmethod def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: """Remove all links from an iterable of styles. Args: segments (Iterable[Segment]): An iterable segments. Yields: Segment: Segments with link removed. """ for segment in segments: if segment.control or segment.style is None: yield segment else: text, style, _control = segment yield cls(text, style.update_link(None) if style else None) @classmethod def strip_styles(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: """Remove all styles from an iterable of segments. Args: segments (Iterable[Segment]): An iterable segments. Yields: Segment: Segments with styles replace with None """ for text, _style, control in segments: yield cls(text, None, control) @classmethod def remove_color(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]: """Remove all color from an iterable of segments. Args: segments (Iterable[Segment]): An iterable segments. Yields: Segment: Segments with colorless style. """ cache: Dict[Style, Style] = {} for text, style, control in segments: if style: colorless_style = cache.get(style) if colorless_style is None: colorless_style = style.without_color cache[style] = colorless_style yield cls(text, colorless_style, control) else: yield cls(text, None, control) @classmethod def divide( cls, segments: Iterable["Segment"], cuts: Iterable[int] ) -> Iterable[List["Segment"]]: """Divides an iterable of segments in to portions. Args: cuts (Iterable[int]): Cell positions where to divide. Yields: [Iterable[List[Segment]]]: An iterable of Segments in List. """ split_segments: List["Segment"] = [] add_segment = split_segments.append iter_cuts = iter(cuts) while True: cut = next(iter_cuts, -1) if cut == -1: return if cut != 0: break yield [] pos = 0 segments_clear = split_segments.clear segments_copy = split_segments.copy _cell_len = cached_cell_len for segment in segments: text, _style, control = segment while text: end_pos = pos if control else pos + _cell_len(text) if end_pos < cut: add_segment(segment) pos = end_pos break if end_pos == cut: add_segment(segment) yield segments_copy() segments_clear() pos = end_pos cut = next(iter_cuts, -1) if cut == -1: if split_segments: yield segments_copy() return break else: before, segment = segment.split_cells(cut - pos) text, _style, control = segment add_segment(before) yield segments_copy() segments_clear() pos = cut cut = next(iter_cuts, -1) if cut == -1: if split_segments: yield segments_copy() return yield segments_copy() class Segments: """A simple renderable to render an iterable of segments. This class may be useful if you want to print segments outside of a __rich_console__ method. Args: segments (Iterable[Segment]): An iterable of segments. new_lines (bool, optional): Add new lines between segments. Defaults to False. """ def __init__(self, segments: Iterable[Segment], new_lines: bool = False) -> None: self.segments = list(segments) self.new_lines = new_lines def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> "RenderResult": if self.new_lines: line = Segment.line() for segment in self.segments: yield segment yield line else: yield from self.segments class SegmentLines: def __init__(self, lines: Iterable[List[Segment]], new_lines: bool = False) -> None: """A simple renderable containing a number of lines of segments. May be used as an intermediate in rendering process. Args: lines (Iterable[List[Segment]]): Lists of segments forming lines. new_lines (bool, optional): Insert new lines after each line. Defaults to False. """ self.lines = list(lines) self.new_lines = new_lines def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> "RenderResult": if self.new_lines: new_line = Segment.line() for line in self.lines: yield from line yield new_line else: for line in self.lines: yield from line if __name__ == "__main__": # pragma: no cover from pip._vendor.rich.console import Console from pip._vendor.rich.syntax import Syntax from pip._vendor.rich.text import Text code = """from rich.console import Console console = Console() text = Text.from_markup("Hello, [bold magenta]World[/]!") console.print(text)""" text = Text.from_markup("Hello, [bold magenta]World[/]!") console = Console() console.rule("rich.Segment") console.print( "A Segment is the last step in the Rich render process before generating text with ANSI codes." ) console.print("\nConsider the following code:\n") console.print(Syntax(code, "python", line_numbers=True)) console.print() console.print( "When you call [b]print()[/b], Rich [i]renders[/i] the object in to the following:\n" ) fragments = list(console.render(text)) console.print(fragments) console.print() console.print("The Segments are then processed to produce the following output:\n") console.print(text) console.print( "\nYou will only need to know this if you are implementing your own Rich renderables." ) rich/live_render.py 0000644 00000007122 15030104210 0010325 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 rich/style.py 0000644 00000064673 15030104210 0007205 0 ustar 00 import sys from functools import lru_cache from marshal import dumps, loads from random import randint from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast from . import errors from .color import Color, ColorParseError, ColorSystem, blend_rgb from .repr import Result, rich_repr from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme # Style instances and style definitions are often interchangeable StyleType = Union[str, "Style"] class _Bit: """A descriptor to get/set a style attribute bit.""" __slots__ = ["bit"] def __init__(self, bit_no: int) -> None: self.bit = 1 << bit_no def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]: if obj._set_attributes & self.bit: return obj._attributes & self.bit != 0 return None @rich_repr class Style: """A terminal style. A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such as bold, italic etc. The attributes have 3 states: they can either be on (``True``), off (``False``), or not set (``None``). Args: color (Union[Color, str], optional): Color of terminal text. Defaults to None. bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None. bold (bool, optional): Enable bold text. Defaults to None. dim (bool, optional): Enable dim text. Defaults to None. italic (bool, optional): Enable italic text. Defaults to None. underline (bool, optional): Enable underlined text. Defaults to None. blink (bool, optional): Enabled blinking text. Defaults to None. blink2 (bool, optional): Enable fast blinking text. Defaults to None. reverse (bool, optional): Enabled reverse text. Defaults to None. conceal (bool, optional): Enable concealed text. Defaults to None. strike (bool, optional): Enable strikethrough text. Defaults to None. underline2 (bool, optional): Enable doubly underlined text. Defaults to None. frame (bool, optional): Enable framed text. Defaults to None. encircle (bool, optional): Enable encircled text. Defaults to None. overline (bool, optional): Enable overlined text. Defaults to None. link (str, link): Link URL. Defaults to None. """ _color: Optional[Color] _bgcolor: Optional[Color] _attributes: int _set_attributes: int _hash: Optional[int] _null: bool _meta: Optional[bytes] __slots__ = [ "_color", "_bgcolor", "_attributes", "_set_attributes", "_link", "_link_id", "_ansi", "_style_definition", "_hash", "_null", "_meta", ] # maps bits on to SGR parameter _style_map = { 0: "1", 1: "2", 2: "3", 3: "4", 4: "5", 5: "6", 6: "7", 7: "8", 8: "9", 9: "21", 10: "51", 11: "52", 12: "53", } STYLE_ATTRIBUTES = { "dim": "dim", "d": "dim", "bold": "bold", "b": "bold", "italic": "italic", "i": "italic", "underline": "underline", "u": "underline", "blink": "blink", "blink2": "blink2", "reverse": "reverse", "r": "reverse", "conceal": "conceal", "c": "conceal", "strike": "strike", "s": "strike", "underline2": "underline2", "uu": "underline2", "frame": "frame", "encircle": "encircle", "overline": "overline", "o": "overline", } def __init__( self, *, color: Optional[Union[Color, str]] = None, bgcolor: Optional[Union[Color, str]] = None, bold: Optional[bool] = None, dim: Optional[bool] = None, italic: Optional[bool] = None, underline: Optional[bool] = None, blink: Optional[bool] = None, blink2: Optional[bool] = None, reverse: Optional[bool] = None, conceal: Optional[bool] = None, strike: Optional[bool] = None, underline2: Optional[bool] = None, frame: Optional[bool] = None, encircle: Optional[bool] = None, overline: Optional[bool] = None, link: Optional[str] = None, meta: Optional[Dict[str, Any]] = None, ): self._ansi: Optional[str] = None self._style_definition: Optional[str] = None def _make_color(color: Union[Color, str]) -> Color: return color if isinstance(color, Color) else Color.parse(color) self._color = None if color is None else _make_color(color) self._bgcolor = None if bgcolor is None else _make_color(bgcolor) self._set_attributes = sum( ( bold is not None, dim is not None and 2, italic is not None and 4, underline is not None and 8, blink is not None and 16, blink2 is not None and 32, reverse is not None and 64, conceal is not None and 128, strike is not None and 256, underline2 is not None and 512, frame is not None and 1024, encircle is not None and 2048, overline is not None and 4096, ) ) self._attributes = ( sum( ( bold and 1 or 0, dim and 2 or 0, italic and 4 or 0, underline and 8 or 0, blink and 16 or 0, blink2 and 32 or 0, reverse and 64 or 0, conceal and 128 or 0, strike and 256 or 0, underline2 and 512 or 0, frame and 1024 or 0, encircle and 2048 or 0, overline and 4096 or 0, ) ) if self._set_attributes else 0 ) self._link = link self._meta = None if meta is None else dumps(meta) self._link_id = ( f"{randint(0, 999999)}{hash(self._meta)}" if (link or meta) else "" ) self._hash: Optional[int] = None self._null = not (self._set_attributes or color or bgcolor or link or meta) @classmethod def null(cls) -> "Style": """Create an 'null' style, equivalent to Style(), but more performant.""" return NULL_STYLE @classmethod def from_color( cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None ) -> "Style": """Create a new style with colors and no attributes. Returns: color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None. bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None. """ style: Style = cls.__new__(Style) style._ansi = None style._style_definition = None style._color = color style._bgcolor = bgcolor style._set_attributes = 0 style._attributes = 0 style._link = None style._link_id = "" style._meta = None style._null = not (color or bgcolor) style._hash = None return style @classmethod def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style": """Create a new style with meta data. Returns: meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None. """ style: Style = cls.__new__(Style) style._ansi = None style._style_definition = None style._color = None style._bgcolor = None style._set_attributes = 0 style._attributes = 0 style._link = None style._meta = dumps(meta) style._link_id = f"{randint(0, 999999)}{hash(style._meta)}" style._hash = None style._null = not (meta) return style @classmethod def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style": """Create a blank style with meta information. Example: style = Style.on(click=self.on_click) Args: meta (Optional[Dict[str, Any]], optional): An optional dict of meta information. **handlers (Any): Keyword arguments are translated in to handlers. Returns: Style: A Style with meta information attached. """ meta = {} if meta is None else meta meta.update({f"@{key}": value for key, value in handlers.items()}) return cls.from_meta(meta) bold = _Bit(0) dim = _Bit(1) italic = _Bit(2) underline = _Bit(3) blink = _Bit(4) blink2 = _Bit(5) reverse = _Bit(6) conceal = _Bit(7) strike = _Bit(8) underline2 = _Bit(9) frame = _Bit(10) encircle = _Bit(11) overline = _Bit(12) @property def link_id(self) -> str: """Get a link id, used in ansi code for links.""" return self._link_id def __str__(self) -> str: """Re-generate style definition from attributes.""" if self._style_definition is None: attributes: List[str] = [] append = attributes.append bits = self._set_attributes if bits & 0b0000000001111: if bits & 1: append("bold" if self.bold else "not bold") if bits & (1 << 1): append("dim" if self.dim else "not dim") if bits & (1 << 2): append("italic" if self.italic else "not italic") if bits & (1 << 3): append("underline" if self.underline else "not underline") if bits & 0b0000111110000: if bits & (1 << 4): append("blink" if self.blink else "not blink") if bits & (1 << 5): append("blink2" if self.blink2 else "not blink2") if bits & (1 << 6): append("reverse" if self.reverse else "not reverse") if bits & (1 << 7): append("conceal" if self.conceal else "not conceal") if bits & (1 << 8): append("strike" if self.strike else "not strike") if bits & 0b1111000000000: if bits & (1 << 9): append("underline2" if self.underline2 else "not underline2") if bits & (1 << 10): append("frame" if self.frame else "not frame") if bits & (1 << 11): append("encircle" if self.encircle else "not encircle") if bits & (1 << 12): append("overline" if self.overline else "not overline") if self._color is not None: append(self._color.name) if self._bgcolor is not None: append("on") append(self._bgcolor.name) if self._link: append("link") append(self._link) self._style_definition = " ".join(attributes) or "none" return self._style_definition def __bool__(self) -> bool: """A Style is false if it has no attributes, colors, or links.""" return not self._null def _make_ansi_codes(self, color_system: ColorSystem) -> str: """Generate ANSI codes for this style. Args: color_system (ColorSystem): Color system. Returns: str: String containing codes. """ if self._ansi is None: sgr: List[str] = [] append = sgr.append _style_map = self._style_map attributes = self._attributes & self._set_attributes if attributes: if attributes & 1: append(_style_map[0]) if attributes & 2: append(_style_map[1]) if attributes & 4: append(_style_map[2]) if attributes & 8: append(_style_map[3]) if attributes & 0b0000111110000: for bit in range(4, 9): if attributes & (1 << bit): append(_style_map[bit]) if attributes & 0b1111000000000: for bit in range(9, 13): if attributes & (1 << bit): append(_style_map[bit]) if self._color is not None: sgr.extend(self._color.downgrade(color_system).get_ansi_codes()) if self._bgcolor is not None: sgr.extend( self._bgcolor.downgrade(color_system).get_ansi_codes( foreground=False ) ) self._ansi = ";".join(sgr) return self._ansi @classmethod @lru_cache(maxsize=1024) def normalize(cls, style: str) -> str: """Normalize a style definition so that styles with the same effect have the same string representation. Args: style (str): A style definition. Returns: str: Normal form of style definition. """ try: return str(cls.parse(style)) except errors.StyleSyntaxError: return style.strip().lower() @classmethod def pick_first(cls, *values: Optional[StyleType]) -> StyleType: """Pick first non-None style.""" for value in values: if value is not None: return value raise ValueError("expected at least one non-None style") def __rich_repr__(self) -> Result: yield "color", self.color, None yield "bgcolor", self.bgcolor, None yield "bold", self.bold, None, yield "dim", self.dim, None, yield "italic", self.italic, None yield "underline", self.underline, None, yield "blink", self.blink, None yield "blink2", self.blink2, None yield "reverse", self.reverse, None yield "conceal", self.conceal, None yield "strike", self.strike, None yield "underline2", self.underline2, None yield "frame", self.frame, None yield "encircle", self.encircle, None yield "link", self.link, None if self._meta: yield "meta", self.meta def __eq__(self, other: Any) -> bool: if not isinstance(other, Style): return NotImplemented return self.__hash__() == other.__hash__() def __ne__(self, other: Any) -> bool: if not isinstance(other, Style): return NotImplemented return self.__hash__() != other.__hash__() def __hash__(self) -> int: if self._hash is not None: return self._hash self._hash = hash( ( self._color, self._bgcolor, self._attributes, self._set_attributes, self._link, self._meta, ) ) return self._hash @property def color(self) -> Optional[Color]: """The foreground color or None if it is not set.""" return self._color @property def bgcolor(self) -> Optional[Color]: """The background color or None if it is not set.""" return self._bgcolor @property def link(self) -> Optional[str]: """Link text, if set.""" return self._link @property def transparent_background(self) -> bool: """Check if the style specified a transparent background.""" return self.bgcolor is None or self.bgcolor.is_default @property def background_style(self) -> "Style": """A Style with background only.""" return Style(bgcolor=self.bgcolor) @property def meta(self) -> Dict[str, Any]: """Get meta information (can not be changed after construction).""" return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta)) @property def without_color(self) -> "Style": """Get a copy of the style with color removed.""" if self._null: return NULL_STYLE style: Style = self.__new__(Style) style._ansi = None style._style_definition = None style._color = None style._bgcolor = None style._attributes = self._attributes style._set_attributes = self._set_attributes style._link = self._link style._link_id = f"{randint(0, 999999)}" if self._link else "" style._null = False style._meta = None style._hash = None return style @classmethod @lru_cache(maxsize=4096) def parse(cls, style_definition: str) -> "Style": """Parse a style definition. Args: style_definition (str): A string containing a style. Raises: errors.StyleSyntaxError: If the style definition syntax is invalid. Returns: `Style`: A Style instance. """ if style_definition.strip() == "none" or not style_definition: return cls.null() STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES color: Optional[str] = None bgcolor: Optional[str] = None attributes: Dict[str, Optional[Any]] = {} link: Optional[str] = None words = iter(style_definition.split()) for original_word in words: word = original_word.lower() if word == "on": word = next(words, "") if not word: raise errors.StyleSyntaxError("color expected after 'on'") try: Color.parse(word) 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 @lru_cache(maxsize=128) def clear_meta_and_links(self) -> "Style": """Get a copy of this style with link and meta information removed. Returns: Style: New style object. """ if self._null: return NULL_STYLE style: Style = self.__new__(Style) style._ansi = self._ansi style._style_definition = self._style_definition style._color = self._color style._bgcolor = self._bgcolor style._attributes = self._attributes style._set_attributes = self._set_attributes style._link = None style._link_id = "" style._hash = None style._null = False style._meta = None return style def update_link(self, link: Optional[str] = None) -> "Style": """Get a copy with a different value for link. Args: link (str, optional): New value for link. Defaults to None. Returns: Style: A new Style instance. """ style: Style = self.__new__(Style) style._ansi = self._ansi style._style_definition = self._style_definition style._color = self._color style._bgcolor = self._bgcolor style._attributes = self._attributes style._set_attributes = self._set_attributes style._link = link style._link_id = f"{randint(0, 999999)}" if link else "" style._hash = None style._null = False style._meta = self._meta return style def render( self, text: str = "", *, color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR, legacy_windows: bool = False, ) -> str: """Render the ANSI codes for the style. Args: text (str, optional): A string to style. Defaults to "". color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR. Returns: str: A string containing ANSI style codes. """ if not text or color_system is None: return text attrs = self._ansi or self._make_ansi_codes(color_system) rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text if self._link and not legacy_windows: rendered = ( f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\" ) return rendered def test(self, text: Optional[str] = None) -> None: """Write text with style directly to terminal. This method is for testing purposes only. Args: text (Optional[str], optional): Text to style or None for style name. """ text = text or str(self) sys.stdout.write(f"{self.render(text)}\n") @lru_cache(maxsize=1024) def _add(self, style: Optional["Style"]) -> "Style": if style is None or style._null: return self if self._null: return style new_style: Style = self.__new__(Style) new_style._ansi = None new_style._style_definition = None new_style._color = style._color or self._color new_style._bgcolor = style._bgcolor or self._bgcolor new_style._attributes = (self._attributes & ~style._set_attributes) | ( style._attributes & style._set_attributes ) new_style._set_attributes = self._set_attributes | style._set_attributes new_style._link = style._link or self._link new_style._link_id = style._link_id or self._link_id new_style._null = style._null if self._meta and style._meta: new_style._meta = dumps({**self.meta, **style.meta}) else: new_style._meta = self._meta or style._meta new_style._hash = None return new_style def __add__(self, style: Optional["Style"]) -> "Style": combined_style = self._add(style) return combined_style.copy() if combined_style.link else combined_style NULL_STYLE = Style() class StyleStack: """A stack of styles.""" __slots__ = ["_stack"] def __init__(self, default_style: "Style") -> None: self._stack: List[Style] = [default_style] def __repr__(self) -> str: return f"<stylestack {self._stack!r}>" @property def current(self) -> Style: """Get the Style at the top of the stack.""" return self._stack[-1] def push(self, style: Style) -> None: """Push a new style on to the stack. Args: style (Style): New style to combine with current style. """ self._stack.append(self._stack[-1] + style) def pop(self) -> Style: """Pop last style and discard. Returns: Style: New current style (also available as stack.current) """ self._stack.pop() return self._stack[-1] rich/_null_file.py 0000644 00000002562 15030104210 0010142 0 ustar 00 from types import TracebackType from typing import IO, Iterable, Iterator, List, Optional, Type class NullFile(IO[str]): def close(self) -> None: pass def isatty(self) -> bool: return False def read(self, __n: int = 1) -> str: return "" def readable(self) -> bool: return False def readline(self, __limit: int = 1) -> str: return "" def readlines(self, __hint: int = 1) -> List[str]: return [] def seek(self, __offset: int, __whence: int = 1) -> int: return 0 def seekable(self) -> bool: return False def tell(self) -> int: return 0 def truncate(self, __size: Optional[int] = 1) -> int: return 0 def writable(self) -> bool: return False def writelines(self, __lines: Iterable[str]) -> None: pass def __next__(self) -> str: return "" def __iter__(self) -> Iterator[str]: return iter([""]) def __enter__(self) -> IO[str]: return self def __exit__( self, __t: Optional[Type[BaseException]], __value: Optional[BaseException], __traceback: Optional[TracebackType], ) -> None: pass def write(self, text: str) -> int: return 0 def flush(self) -> None: pass def fileno(self) -> int: return -1 NULL_FILE = NullFile() rich/progress_bar.py 0000644 00000017742 15030104210 0010530 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. Set to None to render a pulsing animation. 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. Will pulse if a None total was passed. 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.finished". 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: Optional[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) -> Optional[float]: """Calculate percentage complete.""" if self.total is None: return None 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 should_pulse = self.pulse or self.total is None if should_pulse: yield from self._render_pulse(console, width, ascii=ascii) return completed: Optional[float] = ( min(self.total, max(0, self.completed)) if self.total is not None else None ) 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 and completed is not None else width * 2 ) bar_count = complete_halves // 2 half_bar_count = complete_halves % 2 style = console.get_style(self.style) is_finished = self.total is None or self.completed >= self.total complete_style = console.get_style( self.finished_style if is_finished else self.complete_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() rich/json.py 0000644 00000011647 15030104210 0007007 0 ustar 00 from pathlib import Path 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 = False, 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 = False, 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: json_data = Path(args.path).read_text() 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) rich/screen.py 0000644 00000003067 15030104210 0007312 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 rich/_spinners.py 0000644 00000046717 15030104210 0010044 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": " .oO@* "}, "balloon2": {"interval": 120, "frames": ".oO°Oo."}, "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": "dqpb"}, "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": [ "▰▱▱▱▱▱▱", "▰▰▱▱▱▱▱", "▰▰▰▱▱▱▱", "▰▰▰▰▱▱▱", "▰▰▰▰▰▱▱", "▰▰▰▰▰▰▱", "▰▰▰▰▰▰▰", "▰▱▱▱▱▱▱", ], }, } rich/layout.py 0000644 00000033264 15030104210 0007352 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 .region import Region from .repr import Result, rich_repr 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", height=height, ) 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, ) -> 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.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 to 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) rich/status.py 0000644 00000010510 15030104210 0007345 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") rich/__pycache__/protocol.cpython-310.pyc 0000644 00000002465 15030104210 0014234 0 ustar 00 o �ho � @ s` d dl mZmZmZmZ d dlmZ erd dlmZ dZ dede fdd�Zd edd fdd�Z d S )� )�Any�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 �L/usr/local/CyberCP/lib/python3.10/site-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 �inspectr r r r �boolr �objectr r r r r �<module> s rich/__pycache__/jupyter.cpython-310.pyc 0000644 00000007614 15030104210 0014076 0 ustar 00 o �h� � @ s� d dl mZmZmZmZmZmZ erd dlmZ ddl m Z ddlmZ ddl mZ er2d dlmZ dZG dd � d �ZG d d� d�Zdee d efdd�Zdee ded dfdd�Zdeded dfdd�ZdS )� )� TYPE_CHECKING�Any�Dict�Iterable�List�Sequence)�ConsoleRenderable� )�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 �K/usr/local/CyberCP/lib/python3.10/site-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&