codekingpro/portable-devtools
115k
1from dataclasses import dataclass, field, replace2from typing import (3 TYPE_CHECKING,4 Dict,5 Iterable,6 List,7 NamedTuple,8 Optional,9 Sequence,10 Tuple,11 Union,12)13 14from . import box, errors15from ._loop import loop_first_last, loop_last16from ._pick import pick_bool17from ._ratio import ratio_distribute, ratio_reduce18from .align import VerticalAlignMethod19from .jupyter import JupyterMixin20from .measure import Measurement21from .padding import Padding, PaddingDimensions22from .protocol import is_renderable23from .segment import Segment24from .style import Style, StyleType25from .text import Text, TextType26 27if TYPE_CHECKING:28 from .console import (29 Console,30 ConsoleOptions,31 JustifyMethod,32 OverflowMethod,33 RenderableType,34 RenderResult,35 )36 37 38@dataclass39class Column:40 """Defines a column within a ~Table.41 42 Args:43 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.44 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.45 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.46 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.47 box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.48 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.49 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).50 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.51 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.52 show_header (bool, optional): Show a header row. Defaults to True.53 show_footer (bool, optional): Show a footer row. Defaults to False.54 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.55 show_lines (bool, optional): Draw lines between every row. Defaults to False.56 leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.57 style (Union[str, Style], optional): Default style for the table. Defaults to "none".58 row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.59 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".60 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".61 border_style (Union[str, Style], optional): Style of the border. Defaults to None.62 title_style (Union[str, Style], optional): Style of the title. Defaults to None.63 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.64 title_justify (str, optional): Justify method for title. Defaults to "center".65 caption_justify (str, optional): Justify method for caption. Defaults to "center".66 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.67 """68 69 header: "RenderableType" = ""70 """RenderableType: Renderable for the header (typically a string)"""71 72 footer: "RenderableType" = ""73 """RenderableType: Renderable for the footer (typically a string)"""74 75 header_style: StyleType = ""76 """StyleType: The style of the header."""77 78 footer_style: StyleType = ""79 """StyleType: The style of the footer."""80 81 style: StyleType = ""82 """StyleType: The style of the column."""83 84 justify: "JustifyMethod" = "left"85 """str: How to justify text within the column ("left", "center", "right", or "full")"""86 87 vertical: "VerticalAlignMethod" = "top"88 """str: How to vertically align content ("top", "middle", or "bottom")"""89 90 overflow: "OverflowMethod" = "ellipsis"91 """str: Overflow method."""92 93 width: Optional[int] = None94 """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width."""95 96 min_width: Optional[int] = None97 """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None."""98 99 max_width: Optional[int] = None100 """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None."""101 102 ratio: Optional[int] = None103 """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents."""104 105 no_wrap: bool = False106 """bool: Prevent wrapping of text within the column. Defaults to ``False``."""107 108 highlight: bool = False109 """bool: Apply highlighter to column. Defaults to ``False``."""110 111 _index: int = 0112 """Index of column."""113 114 _cells: List["RenderableType"] = field(default_factory=list)115 116 def copy(self) -> "Column":117 """Return a copy of this Column."""118 return replace(self, _cells=[])119 120 @property121 def cells(self) -> Iterable["RenderableType"]:122 """Get all cells in the column, not including header."""123 yield from self._cells124 125 @property126 def flexible(self) -> bool:127 """Check if this column is flexible."""128 return self.ratio is not None129 130 131@dataclass132class Row:133 """Information regarding a row."""134 135 style: Optional[StyleType] = None136 """Style to apply to row."""137 138 end_section: bool = False139 """Indicated end of section, which will force a line beneath the row."""140 141 142class _Cell(NamedTuple):143 """A single cell in a table."""144 145 style: StyleType146 """Style to apply to cell."""147 renderable: "RenderableType"148 """Cell renderable."""149 vertical: VerticalAlignMethod150 """Cell vertical alignment."""151 152 153class Table(JupyterMixin):154 """A console renderable to draw a table.155 156 Args:157 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.158 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.159 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.160 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.161 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.162 box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.163 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.164 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).165 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.166 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.167 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.168 show_header (bool, optional): Show a header row. Defaults to True.169 show_footer (bool, optional): Show a footer row. Defaults to False.170 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.171 show_lines (bool, optional): Draw lines between every row. Defaults to False.172 leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.173 style (Union[str, Style], optional): Default style for the table. Defaults to "none".174 row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.175 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".176 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".177 border_style (Union[str, Style], optional): Style of the border. Defaults to None.178 title_style (Union[str, Style], optional): Style of the title. Defaults to None.179 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.180 title_justify (str, optional): Justify method for title. Defaults to "center".181 caption_justify (str, optional): Justify method for caption. Defaults to "center".182 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.183 """184 185 columns: List[Column]186 rows: List[Row]187 188 def __init__(189 self,190 *headers: Union[Column, str],191 title: Optional[TextType] = None,192 caption: Optional[TextType] = None,193 width: Optional[int] = None,194 min_width: Optional[int] = None,195 box: Optional[box.Box] = box.HEAVY_HEAD,196 safe_box: Optional[bool] = None,197 padding: PaddingDimensions = (0, 1),198 collapse_padding: bool = False,199 pad_edge: bool = True,200 expand: bool = False,201 show_header: bool = True,202 show_footer: bool = False,203 show_edge: bool = True,204 show_lines: bool = False,205 leading: int = 0,206 style: StyleType = "none",207 row_styles: Optional[Iterable[StyleType]] = None,208 header_style: Optional[StyleType] = "table.header",209 footer_style: Optional[StyleType] = "table.footer",210 border_style: Optional[StyleType] = None,211 title_style: Optional[StyleType] = None,212 caption_style: Optional[StyleType] = None,213 title_justify: "JustifyMethod" = "center",214 caption_justify: "JustifyMethod" = "center",215 highlight: bool = False,216 ) -> None:217 self.columns: List[Column] = []218 self.rows: List[Row] = []219 self.title = title220 self.caption = caption221 self.width = width222 self.min_width = min_width223 self.box = box224 self.safe_box = safe_box225 self._padding = Padding.unpack(padding)226 self.pad_edge = pad_edge227 self._expand = expand228 self.show_header = show_header229 self.show_footer = show_footer230 self.show_edge = show_edge231 self.show_lines = show_lines232 self.leading = leading233 self.collapse_padding = collapse_padding234 self.style = style235 self.header_style = header_style or ""236 self.footer_style = footer_style or ""237 self.border_style = border_style238 self.title_style = title_style239 self.caption_style = caption_style240 self.title_justify: "JustifyMethod" = title_justify241 self.caption_justify: "JustifyMethod" = caption_justify242 self.highlight = highlight243 self.row_styles: Sequence[StyleType] = list(row_styles or [])244 append_column = self.columns.append245 for header in headers:246 if isinstance(header, str):247 self.add_column(header=header)248 else:249 header._index = len(self.columns)250 append_column(header)251 252 @classmethod253 def grid(254 cls,255 *headers: Union[Column, str],256 padding: PaddingDimensions = 0,257 collapse_padding: bool = True,258 pad_edge: bool = False,259 expand: bool = False,260 ) -> "Table":261 """Get a table with no lines, headers, or footer.262 263 Args:264 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.265 padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.266 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.267 pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.268 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.269 270 Returns:271 Table: A table instance.272 """273 return cls(274 *headers,275 box=None,276 padding=padding,277 collapse_padding=collapse_padding,278 show_header=False,279 show_footer=False,280 show_edge=False,281 pad_edge=pad_edge,282 expand=expand,283 )284 285 @property286 def expand(self) -> bool:287 """Setting a non-None self.width implies expand."""288 return self._expand or self.width is not None289 290 @expand.setter291 def expand(self, expand: bool) -> None:292 """Set expand."""293 self._expand = expand294 295 @property296 def _extra_width(self) -> int:297 """Get extra width to add to cell content."""298 width = 0299 if self.box and self.show_edge:300 width += 2301 if self.box:302 width += len(self.columns) - 1303 return width304 305 @property306 def row_count(self) -> int:307 """Get the current number of rows."""308 return len(self.rows)309 310 def get_row_style(self, console: "Console", index: int) -> StyleType:311 """Get the current row style."""312 style = Style.null()313 if self.row_styles:314 style += console.get_style(self.row_styles[index % len(self.row_styles)])315 row_style = self.rows[index].style316 if row_style is not None:317 style += console.get_style(row_style)318 return style319 320 def __rich_measure__(321 self, console: "Console", options: "ConsoleOptions"322 ) -> Measurement:323 max_width = options.max_width324 if self.width is not None:325 max_width = self.width326 if max_width < 0:327 return Measurement(0, 0)328 329 extra_width = self._extra_width330 max_width = sum(331 self._calculate_column_widths(332 console, options.update_width(max_width - extra_width)333 )334 )335 _measure_column = self._measure_column336 337 measurements = [338 _measure_column(console, options.update_width(max_width), column)339 for column in self.columns340 ]341 minimum_width = (342 sum(measurement.minimum for measurement in measurements) + extra_width343 )344 maximum_width = (345 sum(measurement.maximum for measurement in measurements) + extra_width346 if (self.width is None)347 else self.width348 )349 measurement = Measurement(minimum_width, maximum_width)350 measurement = measurement.clamp(self.min_width)351 return measurement352 353 @property354 def padding(self) -> Tuple[int, int, int, int]:355 """Get cell padding."""356 return self._padding357 358 @padding.setter359 def padding(self, padding: PaddingDimensions) -> "Table":360 """Set cell padding."""361 self._padding = Padding.unpack(padding)362 return self363 364 def add_column(365 self,366 header: "RenderableType" = "",367 footer: "RenderableType" = "",368 *,369 header_style: Optional[StyleType] = None,370 highlight: Optional[bool] = None,371 footer_style: Optional[StyleType] = None,372 style: Optional[StyleType] = None,373 justify: "JustifyMethod" = "left",374 vertical: "VerticalAlignMethod" = "top",375 overflow: "OverflowMethod" = "ellipsis",376 width: Optional[int] = None,377 min_width: Optional[int] = None,378 max_width: Optional[int] = None,379 ratio: Optional[int] = None,380 no_wrap: bool = False,381 ) -> None:382 """Add a column to the table.383 384 Args:385 header (RenderableType, optional): Text or renderable for the header.386 Defaults to "".387 footer (RenderableType, optional): Text or renderable for the footer.388 Defaults to "".389 header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.390 highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object.391 footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.392 style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.393 justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".394 vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".395 overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".396 width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.397 min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.398 max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.399 ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.400 no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.401 """402 403 column = Column(404 _index=len(self.columns),405 header=header,406 footer=footer,407 header_style=header_style or "",408 highlight=highlight if highlight is not None else self.highlight,409 footer_style=footer_style or "",410 style=style or "",411 justify=justify,412 vertical=vertical,413 overflow=overflow,414 width=width,415 min_width=min_width,416 max_width=max_width,417 ratio=ratio,418 no_wrap=no_wrap,419 )420 self.columns.append(column)421 422 def add_row(423 self,424 *renderables: Optional["RenderableType"],425 style: Optional[StyleType] = None,426 end_section: bool = False,427 ) -> None:428 """Add a row of renderables.429 430 Args:431 *renderables (None or renderable): Each cell in a row must be a renderable object (including str),432 or ``None`` for a blank cell.433 style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.434 end_section (bool, optional): End a section and draw a line. Defaults to False.435 436 Raises:437 errors.NotRenderableError: If you add something that can't be rendered.438 """439 440 def add_cell(column: Column, renderable: "RenderableType") -> None:441 column._cells.append(renderable)442 443 cell_renderables: List[Optional["RenderableType"]] = list(renderables)444 445 columns = self.columns446 if len(cell_renderables) < len(columns):447 cell_renderables = [448 *cell_renderables,449 *[None] * (len(columns) - len(cell_renderables)),450 ]451 for index, renderable in enumerate(cell_renderables):452 if index == len(columns):453 column = Column(_index=index, highlight=self.highlight)454 for _ in self.rows:455 add_cell(column, Text(""))456 self.columns.append(column)457 else:458 column = columns[index]459 if renderable is None:460 add_cell(column, "")461 elif is_renderable(renderable):462 add_cell(column, renderable)463 else:464 raise errors.NotRenderableError(465 f"unable to render {type(renderable).__name__}; a string or other renderable object is required"466 )467 self.rows.append(Row(style=style, end_section=end_section))468 469 def add_section(self) -> None:470 """Add a new section (draw a line after current row)."""471 472 if self.rows:473 self.rows[-1].end_section = True474 475 def __rich_console__(476 self, console: "Console", options: "ConsoleOptions"477 ) -> "RenderResult":478 if not self.columns:479 yield Segment("\n")480 return481 482 max_width = options.max_width483 if self.width is not None:484 max_width = self.width485 486 extra_width = self._extra_width487 488 widths = self._calculate_column_widths(489 console, options.update_width(max_width - extra_width)490 )491 table_width = sum(widths) + extra_width492 493 render_options = options.update(494 width=table_width, highlight=self.highlight, height=None495 )496 497 def render_annotation(498 text: TextType, style: StyleType, justify: "JustifyMethod" = "center"499 ) -> "RenderResult":500 render_text = (501 console.render_str(text, style=style, highlight=False)502 if isinstance(text, str)503 else text504 )505 return console.render(506 render_text, options=render_options.update(justify=justify)507 )508 509 if self.title:510 yield from render_annotation(511 self.title,512 style=Style.pick_first(self.title_style, "table.title"),513 justify=self.title_justify,514 )515 yield from self._render(console, render_options, widths)516 if self.caption:517 yield from render_annotation(518 self.caption,519 style=Style.pick_first(self.caption_style, "table.caption"),520 justify=self.caption_justify,521 )522 523 def _calculate_column_widths(524 self, console: "Console", options: "ConsoleOptions"525 ) -> List[int]:526 """Calculate the widths of each column, including padding, not including borders."""527 max_width = options.max_width528 columns = self.columns529 width_ranges = [530 self._measure_column(console, options, column) for column in columns531 ]532 widths = [_range.maximum or 1 for _range in width_ranges]533 534 get_padding_width = self._get_padding_width535 extra_width = self._extra_width536 if self.expand:537 ratios = [col.ratio or 0 for col in columns if col.flexible]538 if any(ratios):539 fixed_widths = [540 0 if column.flexible else _range.maximum541 for _range, column in zip(width_ranges, columns)542 ]543 flex_minimum = [544 (column.width or 1) + get_padding_width(column._index)545 for column in columns546 if column.flexible547 ]548 flexible_width = max_width - sum(fixed_widths)549 flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)550 iter_flex_widths = iter(flex_widths)551 for index, column in enumerate(columns):552 if column.flexible:553 widths[index] = fixed_widths[index] + next(iter_flex_widths)554 table_width = sum(widths)555 556 if table_width > max_width:557 widths = self._collapse_widths(558 widths,559 [(column.width is None and not column.no_wrap) for column in columns],560 max_width,561 )562 table_width = sum(widths)563 # last resort, reduce columns evenly564 if table_width > max_width:565 excess_width = table_width - max_width566 widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)567 table_width = sum(widths)568 569 width_ranges = [570 self._measure_column(console, options.update_width(width), column)571 for width, column in zip(widths, columns)572 ]573 widths = [_range.maximum or 0 for _range in width_ranges]574 575 if (table_width < max_width and self.expand) or (576 self.min_width is not None and table_width < (self.min_width - extra_width)577 ):578 _max_width = (579 max_width580 if self.min_width is None581 else min(self.min_width - extra_width, max_width)582 )583 pad_widths = ratio_distribute(_max_width - table_width, widths)584 widths = [_width + pad for _width, pad in zip(widths, pad_widths)]585 586 return widths587 588 @classmethod589 def _collapse_widths(590 cls, widths: List[int], wrapable: List[bool], max_width: int591 ) -> List[int]:592 """Reduce widths so that the total is under max_width.593 594 Args:595 widths (List[int]): List of widths.596 wrapable (List[bool]): List of booleans that indicate if a column may shrink.597 max_width (int): Maximum width to reduce to.598 599 Returns:600 List[int]: A new list of widths.601 """602 total_width = sum(widths)603 excess_width = total_width - max_width604 if any(wrapable):605 while total_width and excess_width > 0:606 max_column = max(607 width for width, allow_wrap in zip(widths, wrapable) if allow_wrap608 )609 second_max_column = max(610 width if allow_wrap and width != max_column else 0611 for width, allow_wrap in zip(widths, wrapable)612 )613 column_difference = max_column - second_max_column614 ratios = [615 (1 if (width == max_column and allow_wrap) else 0)616 for width, allow_wrap in zip(widths, wrapable)617 ]618 if not any(ratios) or not column_difference:619 break620 max_reduce = [min(excess_width, column_difference)] * len(widths)621 widths = ratio_reduce(excess_width, ratios, max_reduce, widths)622 623 total_width = sum(widths)624 excess_width = total_width - max_width625 return widths626 627 def _get_cells(628 self, console: "Console", column_index: int, column: Column629 ) -> Iterable[_Cell]:630 """Get all the cells with padding and optional header."""631 632 collapse_padding = self.collapse_padding633 pad_edge = self.pad_edge634 padding = self.padding635 any_padding = any(padding)636 637 first_column = column_index == 0638 last_column = column_index == len(self.columns) - 1639 640 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}641 642 def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:643 cached = _padding_cache.get((first_row, last_row))644 if cached:645 return cached646 top, right, bottom, left = padding647 648 if collapse_padding:649 if not first_column:650 left = max(0, left - right)651 if not last_row:652 bottom = max(0, top - bottom)653 654 if not pad_edge:655 if first_column:656 left = 0657 if last_column:658 right = 0659 if first_row:660 top = 0661 if last_row:662 bottom = 0663 _padding = (top, right, bottom, left)664 _padding_cache[(first_row, last_row)] = _padding665 return _padding666 667 raw_cells: List[Tuple[StyleType, "RenderableType"]] = []668 _append = raw_cells.append669 get_style = console.get_style670 if self.show_header:671 header_style = get_style(self.header_style or "") + get_style(672 column.header_style673 )674 _append((header_style, column.header))675 cell_style = get_style(column.style or "")676 for cell in column.cells:677 _append((cell_style, cell))678 if self.show_footer:679 footer_style = get_style(self.footer_style or "") + get_style(680 column.footer_style681 )682 _append((footer_style, column.footer))683 684 if any_padding:685 _Padding = Padding686 for first, last, (style, renderable) in loop_first_last(raw_cells):687 yield _Cell(688 style,689 _Padding(renderable, get_padding(first, last)),690 getattr(renderable, "vertical", None) or column.vertical,691 )692 else:693 for style, renderable in raw_cells:694 yield _Cell(695 style,696 renderable,697 getattr(renderable, "vertical", None) or column.vertical,698 )699 700 def _get_padding_width(self, column_index: int) -> int:701 """Get extra width from padding."""702 _, pad_right, _, pad_left = self.padding703 704 if self.collapse_padding:705 pad_left = 0706 pad_right = abs(pad_left - pad_right)707 708 if not self.pad_edge:709 if column_index == 0:710 pad_left = 0711 if column_index == len(self.columns) - 1:712 pad_right = 0713 714 return pad_left + pad_right715 716 def _measure_column(717 self,718 console: "Console",719 options: "ConsoleOptions",720 column: Column,721 ) -> Measurement:722 """Get the minimum and maximum width of the column."""723 724 max_width = options.max_width725 if max_width < 1:726 return Measurement(0, 0)727 728 padding_width = self._get_padding_width(column._index)729 if column.width is not None:730 # Fixed width column731 return Measurement(732 column.width + padding_width, column.width + padding_width733 ).with_maximum(max_width)734 # Flexible column, we need to measure contents735 min_widths: List[int] = []736 max_widths: List[int] = []737 append_min = min_widths.append738 append_max = max_widths.append739 get_render_width = Measurement.get740 for cell in self._get_cells(console, column._index, column):741 _min, _max = get_render_width(console, options, cell.renderable)742 append_min(_min)743 append_max(_max)744 745 measurement = Measurement(746 max(min_widths) if min_widths else 1,747 max(max_widths) if max_widths else max_width,748 ).with_maximum(max_width)749 measurement = measurement.clamp(750 None if column.min_width is None else column.min_width + padding_width,751 None if column.max_width is None else column.max_width + padding_width,752 )753 return measurement754 755 def _render(756 self, console: "Console", options: "ConsoleOptions", widths: List[int]757 ) -> "RenderResult":758 table_style = console.get_style(self.style or "")759 760 border_style = table_style + console.get_style(self.border_style or "")761 _column_cells = (762 self._get_cells(console, column_index, column)763 for column_index, column in enumerate(self.columns)764 )765 766 row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))767 _box = (768 self.box.substitute(769 options, safe=pick_bool(self.safe_box, console.safe_box)770 )771 if self.box772 else None773 )774 _box = _box.get_plain_headed_box() if _box and not self.show_header else _box775 776 new_line = Segment.line()777 778 columns = self.columns779 show_header = self.show_header780 show_footer = self.show_footer781 show_edge = self.show_edge782 show_lines = self.show_lines783 leading = self.leading784 785 _Segment = Segment786 if _box:787 box_segments = [788 (789 _Segment(_box.head_left, border_style),790 _Segment(_box.head_right, border_style),791 _Segment(_box.head_vertical, border_style),792 ),793 (794 _Segment(_box.mid_left, border_style),795 _Segment(_box.mid_right, border_style),796 _Segment(_box.mid_vertical, border_style),797 ),798 (799 _Segment(_box.foot_left, border_style),800 _Segment(_box.foot_right, border_style),801 _Segment(_box.foot_vertical, border_style),802 ),803 ]804 if show_edge:805 yield _Segment(_box.get_top(widths), border_style)806 yield new_line807 else:808 box_segments = []809 810 get_row_style = self.get_row_style811 get_style = console.get_style812 813 for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):814 header_row = first and show_header815 footer_row = last and show_footer816 row = (817 self.rows[index - show_header]818 if (not header_row and not footer_row)819 else None820 )821 max_height = 1822 cells: List[List[List[Segment]]] = []823 if header_row or footer_row:824 row_style = Style.null()825 else:826 row_style = get_style(827 get_row_style(console, index - 1 if show_header else index)828 )829 for width, cell, column in zip(widths, row_cell, columns):830 render_options = options.update(831 width=width,832 justify=column.justify,833 no_wrap=column.no_wrap,834 overflow=column.overflow,835 height=None,836 highlight=column.highlight,837 )838 lines = console.render_lines(839 cell.renderable,840 render_options,841 style=get_style(cell.style) + row_style,842 )843 max_height = max(max_height, len(lines))844 cells.append(lines)845 846 row_height = max(len(cell) for cell in cells)847 848 def align_cell(849 cell: List[List[Segment]],850 vertical: "VerticalAlignMethod",851 width: int,852 style: Style,853 ) -> List[List[Segment]]:854 if header_row:855 vertical = "bottom"856 elif footer_row:857 vertical = "top"858 859 if vertical == "top":860 return _Segment.align_top(cell, width, row_height, style)861 elif vertical == "middle":862 return _Segment.align_middle(cell, width, row_height, style)863 return _Segment.align_bottom(cell, width, row_height, style)864 865 cells[:] = [866 _Segment.set_shape(867 align_cell(868 cell,869 _cell.vertical,870 width,871 get_style(_cell.style) + row_style,872 ),873 width,874 max_height,875 )876 for width, _cell, cell, column in zip(widths, row_cell, cells, columns)877 ]878 879 if _box:880 if last and show_footer:881 yield _Segment(882 _box.get_row(widths, "foot", edge=show_edge), border_style883 )884 yield new_line885 left, right, _divider = box_segments[0 if first else (2 if last else 1)]886 887 # If the column divider is whitespace also style it with the row background888 divider = (889 _divider890 if _divider.text.strip()891 else _Segment(892 _divider.text, row_style.background_style + _divider.style893 )894 )895 for line_no in range(max_height):896 if show_edge:897 yield left898 for last_cell, rendered_cell in loop_last(cells):899 yield from rendered_cell[line_no]900 if not last_cell:901 yield divider902 if show_edge:903 yield right904 yield new_line905 else:906 for line_no in range(max_height):907 for rendered_cell in cells:908 yield from rendered_cell[line_no]909 yield new_line910 if _box and first and show_header:911 yield _Segment(912 _box.get_row(widths, "head", edge=show_edge), border_style913 )914 yield new_line915 end_section = row and row.end_section916 if _box and (show_lines or leading or end_section):917 if (918 not last919 and not (show_footer and index >= len(row_cells) - 2)920 and not (show_header and header_row)921 ):922 if leading:923 yield _Segment(924 _box.get_row(widths, "mid", edge=show_edge) * leading,925 border_style,926 )927 else:928 yield _Segment(929 _box.get_row(widths, "row", edge=show_edge), border_style930 )931 yield new_line932 933 if _box and show_edge:934 yield _Segment(_box.get_bottom(widths), border_style)935 yield new_line936 937 938if __name__ == "__main__": # pragma: no cover939 from rich.console import Console940 from rich.highlighter import ReprHighlighter941 942 from ._timer import timer943 944 with timer("Table render"):945 table = Table(946 title="Star Wars Movies",947 caption="Rich example table",948 caption_justify="right",949 )950 951 table.add_column(952 "Released", header_style="bright_cyan", style="cyan", no_wrap=True953 )954 table.add_column("Title", style="magenta")955 table.add_column("Box Office", justify="right", style="green")956 957 table.add_row(958 "Dec 20, 2019",959 "Star Wars: The Rise of Skywalker",960 "$952,110,690",961 )962 table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")963 table.add_row(964 "Dec 15, 2017",965 "Star Wars Ep. V111: The Last Jedi",966 "$1,332,539,889",967 style="on black",968 end_section=True,969 )970 table.add_row(971 "Dec 16, 2016",972 "Rogue One: A Star Wars Story",973 "$1,332,439,889",974 )975 976 def header(text: str) -> None:977 console.print()978 console.rule(highlight(text))979 console.print()980 981 console = Console()982 highlight = ReprHighlighter()983 header("Example Table")984 console.print(table, justify="center")985 986 table.expand = True987 header("expand=True")988 console.print(table)989 990 table.width = 50991 header("width=50")992 993 console.print(table, justify="center")994 995 table.width = None996 table.expand = False997 table.row_styles = ["dim", "none"]998 header("row_styles=['dim', 'none']")999 1000 console.print(table, justify="center")1001 1002 table.width = None1003 table.expand = False1004 table.row_styles = ["dim", "none"]1005 table.leading = 11006 header("leading=1, row_styles=['dim', 'none']")1007 console.print(table, justify="center")1008 1009 table.width = None1010 table.expand = False1011 table.row_styles = ["dim", "none"]1012 table.show_lines = True1013 table.leading = 01014 header("show_lines=True, row_styles=['dim', 'none']")1015 console.print(table, justify="center")1016 