Spaces:
Runtime error
Runtime error
from collections import defaultdict | |
from itertools import chain | |
from operator import itemgetter | |
from typing import Dict, Iterable, List, Optional, Tuple | |
from .align import Align, AlignMethod | |
from .console import Console, ConsoleOptions, RenderableType, RenderResult | |
from .constrain import Constrain | |
from .measure import Measurement | |
from .padding import Padding, PaddingDimensions | |
from .table import Table | |
from .text import TextType | |
from .jupyter import JupyterMixin | |
class Columns(JupyterMixin): | |
"""Display renderables in neat columns. | |
Args: | |
renderables (Iterable[RenderableType]): Any number of Rich renderables (including str). | |
width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None. | |
padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1). | |
expand (bool, optional): Expand columns to full width. Defaults to False. | |
equal (bool, optional): Arrange in to equal sized columns. Defaults to False. | |
column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False. | |
right_to_left (bool, optional): Start column from right hand side. Defaults to False. | |
align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None. | |
title (TextType, optional): Optional title for Columns. | |
""" | |
def __init__( | |
self, | |
renderables: Optional[Iterable[RenderableType]] = None, | |
padding: PaddingDimensions = (0, 1), | |
*, | |
width: Optional[int] = None, | |
expand: bool = False, | |
equal: bool = False, | |
column_first: bool = False, | |
right_to_left: bool = False, | |
align: Optional[AlignMethod] = None, | |
title: Optional[TextType] = None, | |
) -> None: | |
self.renderables = list(renderables or []) | |
self.width = width | |
self.padding = padding | |
self.expand = expand | |
self.equal = equal | |
self.column_first = column_first | |
self.right_to_left = right_to_left | |
self.align: Optional[AlignMethod] = align | |
self.title = title | |
def add_renderable(self, renderable: RenderableType) -> None: | |
"""Add a renderable to the columns. | |
Args: | |
renderable (RenderableType): Any renderable object. | |
""" | |
self.renderables.append(renderable) | |
def __rich_console__( | |
self, console: Console, options: ConsoleOptions | |
) -> RenderResult: | |
render_str = console.render_str | |
renderables = [ | |
render_str(renderable) if isinstance(renderable, str) else renderable | |
for renderable in self.renderables | |
] | |
if not renderables: | |
return | |
_top, right, _bottom, left = Padding.unpack(self.padding) | |
width_padding = max(left, right) | |
max_width = options.max_width | |
widths: Dict[int, int] = defaultdict(int) | |
column_count = len(renderables) | |
get_measurement = Measurement.get | |
renderable_widths = [ | |
get_measurement(console, options, renderable).maximum | |
for renderable in renderables | |
] | |
if self.equal: | |
renderable_widths = [max(renderable_widths)] * len(renderable_widths) | |
def iter_renderables( | |
column_count: int, | |
) -> Iterable[Tuple[int, Optional[RenderableType]]]: | |
item_count = len(renderables) | |
if self.column_first: | |
width_renderables = list(zip(renderable_widths, renderables)) | |
column_lengths: List[int] = [item_count // column_count] * column_count | |
for col_no in range(item_count % column_count): | |
column_lengths[col_no] += 1 | |
row_count = (item_count + column_count - 1) // column_count | |
cells = [[-1] * column_count for _ in range(row_count)] | |
row = col = 0 | |
for index in range(item_count): | |
cells[row][col] = index | |
column_lengths[col] -= 1 | |
if column_lengths[col]: | |
row += 1 | |
else: | |
col += 1 | |
row = 0 | |
for index in chain.from_iterable(cells): | |
if index == -1: | |
break | |
yield width_renderables[index] | |
else: | |
yield from zip(renderable_widths, renderables) | |
# Pad odd elements with spaces | |
if item_count % column_count: | |
for _ in range(column_count - (item_count % column_count)): | |
yield 0, None | |
table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False) | |
table.expand = self.expand | |
table.title = self.title | |
if self.width is not None: | |
column_count = (max_width) // (self.width + width_padding) | |
for _ in range(column_count): | |
table.add_column(width=self.width) | |
else: | |
while column_count > 1: | |
widths.clear() | |
column_no = 0 | |
for renderable_width, _ in iter_renderables(column_count): | |
widths[column_no] = max(widths[column_no], renderable_width) | |
total_width = sum(widths.values()) + width_padding * ( | |
len(widths) - 1 | |
) | |
if total_width > max_width: | |
column_count = len(widths) - 1 | |
break | |
else: | |
column_no = (column_no + 1) % column_count | |
else: | |
break | |
get_renderable = itemgetter(1) | |
_renderables = [ | |
get_renderable(_renderable) | |
for _renderable in iter_renderables(column_count) | |
] | |
if self.equal: | |
_renderables = [ | |
None | |
if renderable is None | |
else Constrain(renderable, renderable_widths[0]) | |
for renderable in _renderables | |
] | |
if self.align: | |
align = self.align | |
_Align = Align | |
_renderables = [ | |
None if renderable is None else _Align(renderable, align) | |
for renderable in _renderables | |
] | |
right_to_left = self.right_to_left | |
add_row = table.add_row | |
for start in range(0, len(_renderables), column_count): | |
row = _renderables[start : start + column_count] | |
if right_to_left: | |
row = row[::-1] | |
add_row(*row) | |
yield table | |
if __name__ == "__main__": # pragma: no cover | |
import os | |
console = Console() | |
files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))] | |
columns = Columns(files, padding=(0, 1), expand=False, equal=False) | |
console.print(columns) | |
console.rule() | |
columns.column_first = True | |
console.print(columns) | |
columns.right_to_left = True | |
console.rule() | |
console.print(columns) | |