123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968 |
- from dataclasses import dataclass, field, replace
- from typing import (
- TYPE_CHECKING,
- Dict,
- Iterable,
- List,
- NamedTuple,
- Optional,
- Sequence,
- Tuple,
- Union,
- )
- from . import box, errors
- from ._loop import loop_first_last, loop_last
- from ._pick import pick_bool
- from ._ratio import ratio_distribute, ratio_reduce
- from .align import VerticalAlignMethod
- from .jupyter import JupyterMixin
- from .measure import Measurement
- from .padding import Padding, PaddingDimensions
- from .protocol import is_renderable
- from .segment import Segment
- from .style import Style, StyleType
- from .text import Text, TextType
- if TYPE_CHECKING:
- from .console import (
- Console,
- ConsoleOptions,
- JustifyMethod,
- OverflowMethod,
- RenderableType,
- RenderResult,
- )
- @dataclass
- class Column:
- """Defines a column in a table."""
- header: "RenderableType" = ""
- """RenderableType: Renderable for the header (typically a string)"""
- footer: "RenderableType" = ""
- """RenderableType: Renderable for the footer (typically a string)"""
- header_style: StyleType = ""
- """StyleType: The style of the header."""
- footer_style: StyleType = ""
- """StyleType: The style of the footer."""
- style: StyleType = ""
- """StyleType: The style of the column."""
- justify: "JustifyMethod" = "left"
- """str: How to justify text within the column ("left", "center", "right", or "full")"""
- vertical: "VerticalAlignMethod" = "top"
- """str: How to vertically align content ("top", "middle", or "bottom")"""
- overflow: "OverflowMethod" = "ellipsis"
- """str: Overflow method."""
- width: Optional[int] = None
- """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width."""
- min_width: Optional[int] = None
- """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None."""
- max_width: Optional[int] = None
- """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None."""
- ratio: Optional[int] = None
- """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents."""
- no_wrap: bool = False
- """bool: Prevent wrapping of text within the column. Defaults to ``False``."""
- _index: int = 0
- """Index of column."""
- _cells: List["RenderableType"] = field(default_factory=list)
- def copy(self) -> "Column":
- """Return a copy of this Column."""
- return replace(self, _cells=[])
- @property
- def cells(self) -> Iterable["RenderableType"]:
- """Get all cells in the column, not including header."""
- yield from self._cells
- @property
- def flexible(self) -> bool:
- """Check if this column is flexible."""
- return self.ratio is not None
- @dataclass
- class Row:
- """Information regarding a row."""
- style: Optional[StyleType] = None
- """Style to apply to row."""
- end_section: bool = False
- """Indicated end of section, which will force a line beneath the row."""
- class _Cell(NamedTuple):
- """A single cell in a table."""
- style: StyleType
- """Style to apply to cell."""
- renderable: "RenderableType"
- """Cell renderable."""
- vertical: VerticalAlignMethod
- """Cell vertical alignment."""
- class Table(JupyterMixin):
- """A console renderable to draw a table.
- Args:
- *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
- title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
- caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
- width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
- min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
- 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.
- safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
- padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
- collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
- pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
- expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
- show_header (bool, optional): Show a header row. Defaults to True.
- show_footer (bool, optional): Show a footer row. Defaults to False.
- show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
- show_lines (bool, optional): Draw lines between every row. Defaults to False.
- leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
- style (Union[str, Style], optional): Default style for the table. Defaults to "none".
- 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.
- header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
- footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
- border_style (Union[str, Style], optional): Style of the border. Defaults to None.
- title_style (Union[str, Style], optional): Style of the title. Defaults to None.
- caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
- title_justify (str, optional): Justify method for title. Defaults to "center".
- caption_justify (str, optional): Justify method for caption. Defaults to "center".
- highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
- """
- columns: List[Column]
- rows: List[Row]
- def __init__(
- self,
- *headers: Union[Column, str],
- title: Optional[TextType] = None,
- caption: Optional[TextType] = None,
- width: Optional[int] = None,
- min_width: Optional[int] = None,
- box: Optional[box.Box] = box.HEAVY_HEAD,
- safe_box: Optional[bool] = None,
- padding: PaddingDimensions = (0, 1),
- collapse_padding: bool = False,
- pad_edge: bool = True,
- expand: bool = False,
- show_header: bool = True,
- show_footer: bool = False,
- show_edge: bool = True,
- show_lines: bool = False,
- leading: int = 0,
- style: StyleType = "none",
- row_styles: Optional[Iterable[StyleType]] = None,
- header_style: Optional[StyleType] = "table.header",
- footer_style: Optional[StyleType] = "table.footer",
- border_style: Optional[StyleType] = None,
- title_style: Optional[StyleType] = None,
- caption_style: Optional[StyleType] = None,
- title_justify: "JustifyMethod" = "center",
- caption_justify: "JustifyMethod" = "center",
- highlight: bool = False,
- ) -> None:
- self.columns: List[Column] = []
- self.rows: List[Row] = []
- self.title = title
- self.caption = caption
- self.width = width
- self.min_width = min_width
- self.box = box
- self.safe_box = safe_box
- self._padding = Padding.unpack(padding)
- self.pad_edge = pad_edge
- self._expand = expand
- self.show_header = show_header
- self.show_footer = show_footer
- self.show_edge = show_edge
- self.show_lines = show_lines
- self.leading = leading
- self.collapse_padding = collapse_padding
- self.style = style
- self.header_style = header_style or ""
- self.footer_style = footer_style or ""
- self.border_style = border_style
- self.title_style = title_style
- self.caption_style = caption_style
- self.title_justify: "JustifyMethod" = title_justify
- self.caption_justify: "JustifyMethod" = caption_justify
- self.highlight = highlight
- self.row_styles: Sequence[StyleType] = list(row_styles or [])
- append_column = self.columns.append
- for header in headers:
- if isinstance(header, str):
- self.add_column(header=header)
- else:
- header._index = len(self.columns)
- append_column(header)
- @classmethod
- def grid(
- cls,
- *headers: Union[Column, str],
- padding: PaddingDimensions = 0,
- collapse_padding: bool = True,
- pad_edge: bool = False,
- expand: bool = False,
- ) -> "Table":
- """Get a table with no lines, headers, or footer.
- Args:
- *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
- padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.
- collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.
- pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.
- expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
- Returns:
- Table: A table instance.
- """
- return cls(
- *headers,
- box=None,
- padding=padding,
- collapse_padding=collapse_padding,
- show_header=False,
- show_footer=False,
- show_edge=False,
- pad_edge=pad_edge,
- expand=expand,
- )
- @property
- def expand(self) -> bool:
- """Setting a non-None self.width implies expand."""
- return self._expand or self.width is not None
- @expand.setter
- def expand(self, expand: bool) -> None:
- """Set expand."""
- self._expand = expand
- @property
- def _extra_width(self) -> int:
- """Get extra width to add to cell content."""
- width = 0
- if self.box and self.show_edge:
- width += 2
- if self.box:
- width += len(self.columns) - 1
- return width
- @property
- def row_count(self) -> int:
- """Get the current number of rows."""
- return len(self.rows)
- def get_row_style(self, console: "Console", index: int) -> StyleType:
- """Get the current row style."""
- style = Style.null()
- if self.row_styles:
- style += console.get_style(self.row_styles[index % len(self.row_styles)])
- row_style = self.rows[index].style
- if row_style is not None:
- style += console.get_style(row_style)
- return style
- def __rich_measure__(
- self, console: "Console", options: "ConsoleOptions"
- ) -> Measurement:
- max_width = options.max_width
- if self.width is not None:
- max_width = self.width
- if max_width < 0:
- return Measurement(0, 0)
- extra_width = self._extra_width
- max_width = sum(
- self._calculate_column_widths(
- console, options.update_width(max_width - extra_width)
- )
- )
- _measure_column = self._measure_column
- measurements = [
- _measure_column(console, options.update_width(max_width), column)
- for column in self.columns
- ]
- minimum_width = (
- sum(measurement.minimum for measurement in measurements) + extra_width
- )
- maximum_width = (
- sum(measurement.maximum for measurement in measurements) + extra_width
- if (self.width is None)
- else self.width
- )
- measurement = Measurement(minimum_width, maximum_width)
- measurement = measurement.clamp(self.min_width)
- return measurement
- @property
- def padding(self) -> Tuple[int, int, int, int]:
- """Get cell padding."""
- return self._padding
- @padding.setter
- def padding(self, padding: PaddingDimensions) -> "Table":
- """Set cell padding."""
- self._padding = Padding.unpack(padding)
- return self
- def add_column(
- self,
- header: "RenderableType" = "",
- footer: "RenderableType" = "",
- *,
- header_style: Optional[StyleType] = None,
- footer_style: Optional[StyleType] = None,
- style: Optional[StyleType] = None,
- justify: "JustifyMethod" = "left",
- vertical: "VerticalAlignMethod" = "top",
- overflow: "OverflowMethod" = "ellipsis",
- width: Optional[int] = None,
- min_width: Optional[int] = None,
- max_width: Optional[int] = None,
- ratio: Optional[int] = None,
- no_wrap: bool = False,
- ) -> None:
- """Add a column to the table.
- Args:
- header (RenderableType, optional): Text or renderable for the header.
- Defaults to "".
- footer (RenderableType, optional): Text or renderable for the footer.
- Defaults to "".
- header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.
- footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
- style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
- justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
- vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
- overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
- width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
- min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
- max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.
- ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.
- no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
- """
- column = Column(
- _index=len(self.columns),
- header=header,
- footer=footer,
- header_style=header_style or "",
- footer_style=footer_style or "",
- style=style or "",
- justify=justify,
- vertical=vertical,
- overflow=overflow,
- width=width,
- min_width=min_width,
- max_width=max_width,
- ratio=ratio,
- no_wrap=no_wrap,
- )
- self.columns.append(column)
- def add_row(
- self,
- *renderables: Optional["RenderableType"],
- style: Optional[StyleType] = None,
- end_section: bool = False,
- ) -> None:
- """Add a row of renderables.
- Args:
- *renderables (None or renderable): Each cell in a row must be a renderable object (including str),
- or ``None`` for a blank cell.
- style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
- end_section (bool, optional): End a section and draw a line. Defaults to False.
- Raises:
- errors.NotRenderableError: If you add something that can't be rendered.
- """
- def add_cell(column: Column, renderable: "RenderableType") -> None:
- column._cells.append(renderable)
- cell_renderables: List[Optional["RenderableType"]] = list(renderables)
- columns = self.columns
- if len(cell_renderables) < len(columns):
- cell_renderables = [
- *cell_renderables,
- *[None] * (len(columns) - len(cell_renderables)),
- ]
- for index, renderable in enumerate(cell_renderables):
- if index == len(columns):
- column = Column(_index=index)
- for _ in self.rows:
- add_cell(column, Text(""))
- self.columns.append(column)
- else:
- column = columns[index]
- if renderable is None:
- add_cell(column, "")
- elif is_renderable(renderable):
- add_cell(column, renderable)
- else:
- raise errors.NotRenderableError(
- f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
- )
- self.rows.append(Row(style=style, end_section=end_section))
- def __rich_console__(
- self, console: "Console", options: "ConsoleOptions"
- ) -> "RenderResult":
- if not self.columns:
- yield Segment("\n")
- return
- max_width = options.max_width
- if self.width is not None:
- max_width = self.width
- extra_width = self._extra_width
- widths = self._calculate_column_widths(
- console, options.update_width(max_width - extra_width)
- )
- table_width = sum(widths) + extra_width
- render_options = options.update(
- width=table_width, highlight=self.highlight, height=None
- )
- def render_annotation(
- text: TextType, style: StyleType, justify: "JustifyMethod" = "center"
- ) -> "RenderResult":
- render_text = (
- console.render_str(text, style=style, highlight=False)
- if isinstance(text, str)
- else text
- )
- return console.render(
- render_text, options=render_options.update(justify=justify)
- )
- if self.title:
- yield from render_annotation(
- self.title,
- style=Style.pick_first(self.title_style, "table.title"),
- justify=self.title_justify,
- )
- yield from self._render(console, render_options, widths)
- if self.caption:
- yield from render_annotation(
- self.caption,
- style=Style.pick_first(self.caption_style, "table.caption"),
- justify=self.caption_justify,
- )
- def _calculate_column_widths(
- self, console: "Console", options: "ConsoleOptions"
- ) -> List[int]:
- """Calculate the widths of each column, including padding, not including borders."""
- max_width = options.max_width
- columns = self.columns
- width_ranges = [
- self._measure_column(console, options, column) for column in columns
- ]
- widths = [_range.maximum or 1 for _range in width_ranges]
- get_padding_width = self._get_padding_width
- extra_width = self._extra_width
- if self.expand:
- ratios = [col.ratio or 0 for col in columns if col.flexible]
- if any(ratios):
- fixed_widths = [
- 0 if column.flexible else _range.maximum
- for _range, column in zip(width_ranges, columns)
- ]
- flex_minimum = [
- (column.width or 1) + get_padding_width(column._index)
- for column in columns
- if column.flexible
- ]
- flexible_width = max_width - sum(fixed_widths)
- flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)
- iter_flex_widths = iter(flex_widths)
- for index, column in enumerate(columns):
- if column.flexible:
- widths[index] = fixed_widths[index] + next(iter_flex_widths)
- table_width = sum(widths)
- if table_width > max_width:
- widths = self._collapse_widths(
- widths,
- [(column.width is None and not column.no_wrap) for column in columns],
- max_width,
- )
- table_width = sum(widths)
- # last resort, reduce columns evenly
- if table_width > max_width:
- excess_width = table_width - max_width
- widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
- table_width = sum(widths)
- width_ranges = [
- self._measure_column(console, options.update_width(width), column)
- for width, column in zip(widths, columns)
- ]
- widths = [_range.maximum or 0 for _range in width_ranges]
- if (table_width < max_width and self.expand) or (
- self.min_width is not None and table_width < (self.min_width - extra_width)
- ):
- _max_width = (
- max_width
- if self.min_width is None
- else min(self.min_width - extra_width, max_width)
- )
- pad_widths = ratio_distribute(_max_width - table_width, widths)
- widths = [_width + pad for _width, pad in zip(widths, pad_widths)]
- return widths
- @classmethod
- def _collapse_widths(
- cls, widths: List[int], wrapable: List[bool], max_width: int
- ) -> List[int]:
- """Reduce widths so that the total is under max_width.
- Args:
- widths (List[int]): List of widths.
- wrapable (List[bool]): List of booleans that indicate if a column may shrink.
- max_width (int): Maximum width to reduce to.
- Returns:
- List[int]: A new list of widths.
- """
- total_width = sum(widths)
- excess_width = total_width - max_width
- if any(wrapable):
- while total_width and excess_width > 0:
- max_column = max(
- width for width, allow_wrap in zip(widths, wrapable) if allow_wrap
- )
- second_max_column = max(
- width if allow_wrap and width != max_column else 0
- for width, allow_wrap in zip(widths, wrapable)
- )
- column_difference = max_column - second_max_column
- ratios = [
- (1 if (width == max_column and allow_wrap) else 0)
- for width, allow_wrap in zip(widths, wrapable)
- ]
- if not any(ratios) or not column_difference:
- break
- max_reduce = [min(excess_width, column_difference)] * len(widths)
- widths = ratio_reduce(excess_width, ratios, max_reduce, widths)
- total_width = sum(widths)
- excess_width = total_width - max_width
- return widths
- def _get_cells(
- self, console: "Console", column_index: int, column: Column
- ) -> Iterable[_Cell]:
- """Get all the cells with padding and optional header."""
- collapse_padding = self.collapse_padding
- pad_edge = self.pad_edge
- padding = self.padding
- any_padding = any(padding)
- first_column = column_index == 0
- last_column = column_index == len(self.columns) - 1
- _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}
- def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:
- cached = _padding_cache.get((first_row, last_row))
- if cached:
- return cached
- top, right, bottom, left = padding
- if collapse_padding:
- if not first_column:
- left = max(0, left - right)
- if not last_row:
- bottom = max(0, top - bottom)
- if not pad_edge:
- if first_column:
- left = 0
- if last_column:
- right = 0
- if first_row:
- top = 0
- if last_row:
- bottom = 0
- _padding = (top, right, bottom, left)
- _padding_cache[(first_row, last_row)] = _padding
- return _padding
- raw_cells: List[Tuple[StyleType, "RenderableType"]] = []
- _append = raw_cells.append
- get_style = console.get_style
- if self.show_header:
- header_style = get_style(self.header_style or "") + get_style(
- column.header_style
- )
- _append((header_style, column.header))
- cell_style = get_style(column.style or "")
- for cell in column.cells:
- _append((cell_style, cell))
- if self.show_footer:
- footer_style = get_style(self.footer_style or "") + get_style(
- column.footer_style
- )
- _append((footer_style, column.footer))
- if any_padding:
- _Padding = Padding
- for first, last, (style, renderable) in loop_first_last(raw_cells):
- yield _Cell(
- style,
- _Padding(renderable, get_padding(first, last)),
- getattr(renderable, "vertical", None) or column.vertical,
- )
- else:
- for (style, renderable) in raw_cells:
- yield _Cell(
- style,
- renderable,
- getattr(renderable, "vertical", None) or column.vertical,
- )
- def _get_padding_width(self, column_index: int) -> int:
- """Get extra width from padding."""
- _, pad_right, _, pad_left = self.padding
- if self.collapse_padding:
- if column_index > 0:
- pad_left = max(0, pad_left - pad_right)
- return pad_left + pad_right
- def _measure_column(
- self,
- console: "Console",
- options: "ConsoleOptions",
- column: Column,
- ) -> Measurement:
- """Get the minimum and maximum width of the column."""
- max_width = options.max_width
- if max_width < 1:
- return Measurement(0, 0)
- padding_width = self._get_padding_width(column._index)
- if column.width is not None:
- # Fixed width column
- return Measurement(
- column.width + padding_width, column.width + padding_width
- ).with_maximum(max_width)
- # Flexible column, we need to measure contents
- min_widths: List[int] = []
- max_widths: List[int] = []
- append_min = min_widths.append
- append_max = max_widths.append
- get_render_width = Measurement.get
- for cell in self._get_cells(console, column._index, column):
- _min, _max = get_render_width(console, options, cell.renderable)
- append_min(_min)
- append_max(_max)
- measurement = Measurement(
- max(min_widths) if min_widths else 1,
- max(max_widths) if max_widths else max_width,
- ).with_maximum(max_width)
- measurement = measurement.clamp(
- None if column.min_width is None else column.min_width + padding_width,
- None if column.max_width is None else column.max_width + padding_width,
- )
- return measurement
- def _render(
- self, console: "Console", options: "ConsoleOptions", widths: List[int]
- ) -> "RenderResult":
- table_style = console.get_style(self.style or "")
- border_style = table_style + console.get_style(self.border_style or "")
- _column_cells = (
- self._get_cells(console, column_index, column)
- for column_index, column in enumerate(self.columns)
- )
- row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))
- _box = (
- self.box.substitute(
- options, safe=pick_bool(self.safe_box, console.safe_box)
- )
- if self.box
- else None
- )
- # _box = self.box
- new_line = Segment.line()
- columns = self.columns
- show_header = self.show_header
- show_footer = self.show_footer
- show_edge = self.show_edge
- show_lines = self.show_lines
- leading = self.leading
- _Segment = Segment
- if _box:
- box_segments = [
- (
- _Segment(_box.head_left, border_style),
- _Segment(_box.head_right, border_style),
- _Segment(_box.head_vertical, border_style),
- ),
- (
- _Segment(_box.foot_left, border_style),
- _Segment(_box.foot_right, border_style),
- _Segment(_box.foot_vertical, border_style),
- ),
- (
- _Segment(_box.mid_left, border_style),
- _Segment(_box.mid_right, border_style),
- _Segment(_box.mid_vertical, border_style),
- ),
- ]
- if show_edge:
- yield _Segment(_box.get_top(widths), border_style)
- yield new_line
- else:
- box_segments = []
- get_row_style = self.get_row_style
- get_style = console.get_style
- for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):
- header_row = first and show_header
- footer_row = last and show_footer
- row = (
- self.rows[index - show_header]
- if (not header_row and not footer_row)
- else None
- )
- max_height = 1
- cells: List[List[List[Segment]]] = []
- if header_row or footer_row:
- row_style = Style.null()
- else:
- row_style = get_style(
- get_row_style(console, index - 1 if show_header else index)
- )
- for width, cell, column in zip(widths, row_cell, columns):
- render_options = options.update(
- width=width,
- justify=column.justify,
- no_wrap=column.no_wrap,
- overflow=column.overflow,
- height=None,
- )
- lines = console.render_lines(
- cell.renderable,
- render_options,
- style=get_style(cell.style) + row_style,
- )
- max_height = max(max_height, len(lines))
- cells.append(lines)
- row_height = max(len(cell) for cell in cells)
- def align_cell(
- cell: List[List[Segment]],
- vertical: "VerticalAlignMethod",
- width: int,
- style: Style,
- ) -> List[List[Segment]]:
- if header_row:
- vertical = "bottom"
- elif footer_row:
- vertical = "top"
- if vertical == "top":
- return _Segment.align_top(cell, width, row_height, style)
- elif vertical == "middle":
- return _Segment.align_middle(cell, width, row_height, style)
- return _Segment.align_bottom(cell, width, row_height, style)
- cells[:] = [
- _Segment.set_shape(
- align_cell(
- cell,
- _cell.vertical,
- width,
- get_style(_cell.style) + row_style,
- ),
- width,
- max_height,
- )
- for width, _cell, cell, column in zip(widths, row_cell, cells, columns)
- ]
- if _box:
- if last and show_footer:
- yield _Segment(
- _box.get_row(widths, "foot", edge=show_edge), border_style
- )
- yield new_line
- left, right, _divider = box_segments[0 if first else (2 if last else 1)]
- # If the column divider is whitespace also style it with the row background
- divider = (
- _divider
- if _divider.text.strip()
- else _Segment(
- _divider.text, row_style.background_style + _divider.style
- )
- )
- for line_no in range(max_height):
- if show_edge:
- yield left
- for last_cell, rendered_cell in loop_last(cells):
- yield from rendered_cell[line_no]
- if not last_cell:
- yield divider
- if show_edge:
- yield right
- yield new_line
- else:
- for line_no in range(max_height):
- for rendered_cell in cells:
- yield from rendered_cell[line_no]
- yield new_line
- if _box and first and show_header:
- yield _Segment(
- _box.get_row(widths, "head", edge=show_edge), border_style
- )
- yield new_line
- end_section = row and row.end_section
- if _box and (show_lines or leading or end_section):
- if (
- not last
- and not (show_footer and index >= len(row_cells) - 2)
- and not (show_header and header_row)
- ):
- if leading:
- yield _Segment(
- _box.get_row(widths, "mid", edge=show_edge) * leading,
- border_style,
- )
- else:
- yield _Segment(
- _box.get_row(widths, "row", edge=show_edge), border_style
- )
- yield new_line
- if _box and show_edge:
- yield _Segment(_box.get_bottom(widths), border_style)
- yield new_line
- if __name__ == "__main__": # pragma: no cover
- from pip._vendor.rich.console import Console
- from pip._vendor.rich.highlighter import ReprHighlighter
- from pip._vendor.rich.table import Table as Table
- from ._timer import timer
- with timer("Table render"):
- table = Table(
- title="Star Wars Movies",
- caption="Rich example table",
- caption_justify="right",
- )
- table.add_column(
- "Released", header_style="bright_cyan", style="cyan", no_wrap=True
- )
- table.add_column("Title", style="magenta")
- table.add_column("Box Office", justify="right", style="green")
- table.add_row(
- "Dec 20, 2019",
- "Star Wars: The Rise of Skywalker",
- "$952,110,690",
- )
- table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
- table.add_row(
- "Dec 15, 2017",
- "Star Wars Ep. V111: The Last Jedi",
- "$1,332,539,889",
- style="on black",
- end_section=True,
- )
- table.add_row(
- "Dec 16, 2016",
- "Rogue One: A Star Wars Story",
- "$1,332,439,889",
- )
- def header(text: str) -> None:
- console.print()
- console.rule(highlight(text))
- console.print()
- console = Console()
- highlight = ReprHighlighter()
- header("Example Table")
- console.print(table, justify="center")
- table.expand = True
- header("expand=True")
- console.print(table)
- table.width = 50
- header("width=50")
- console.print(table, justify="center")
- table.width = None
- table.expand = False
- table.row_styles = ["dim", "none"]
- header("row_styles=['dim', 'none']")
- console.print(table, justify="center")
- table.width = None
- table.expand = False
- table.row_styles = ["dim", "none"]
- table.leading = 1
- header("leading=1, row_styles=['dim', 'none']")
- console.print(table, justify="center")
- table.width = None
- table.expand = False
- table.row_styles = ["dim", "none"]
- table.show_lines = True
- table.leading = 0
- header("show_lines=True, row_styles=['dim', 'none']")
- console.print(table, justify="center")
|