Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/table.py: 20%

388 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1from dataclasses import dataclass, field, replace 

2from 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, errors 

15from ._loop import loop_first_last, loop_last 

16from ._pick import pick_bool 

17from ._ratio import ratio_distribute, ratio_reduce 

18from .align import VerticalAlignMethod 

19from .jupyter import JupyterMixin 

20from .measure import Measurement 

21from .padding import Padding, PaddingDimensions 

22from .protocol import is_renderable 

23from .segment import Segment 

24from .style import Style, StyleType 

25from .text import Text, TextType 

26 

27if TYPE_CHECKING: 

28 from .console import ( 

29 Console, 

30 ConsoleOptions, 

31 JustifyMethod, 

32 OverflowMethod, 

33 RenderableType, 

34 RenderResult, 

35 ) 

36 

37 

38@dataclass 

39class 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 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. 

53 show_header (bool, optional): Show a header row. Defaults to True. 

54 show_footer (bool, optional): Show a footer row. Defaults to False. 

55 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. 

56 show_lines (bool, optional): Draw lines between every row. Defaults to False. 

57 leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. 

58 style (Union[str, Style], optional): Default style for the table. Defaults to "none". 

59 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. 

60 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". 

61 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". 

62 border_style (Union[str, Style], optional): Style of the border. Defaults to None. 

63 title_style (Union[str, Style], optional): Style of the title. Defaults to None. 

64 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. 

65 title_justify (str, optional): Justify method for title. Defaults to "center". 

66 caption_justify (str, optional): Justify method for caption. Defaults to "center". 

67 highlight (bool, optional): Highlight cell contents (if str). Defaults to False. 

68 """ 

69 

70 header: "RenderableType" = "" 

71 """RenderableType: Renderable for the header (typically a string)""" 

72 

73 footer: "RenderableType" = "" 

74 """RenderableType: Renderable for the footer (typically a string)""" 

75 

76 header_style: StyleType = "" 

77 """StyleType: The style of the header.""" 

78 

79 footer_style: StyleType = "" 

80 """StyleType: The style of the footer.""" 

81 

82 style: StyleType = "" 

83 """StyleType: The style of the column.""" 

84 

85 justify: "JustifyMethod" = "left" 

86 """str: How to justify text within the column ("left", "center", "right", or "full")""" 

87 

88 vertical: "VerticalAlignMethod" = "top" 

89 """str: How to vertically align content ("top", "middle", or "bottom")""" 

90 

91 overflow: "OverflowMethod" = "ellipsis" 

92 """str: Overflow method.""" 

93 

94 width: Optional[int] = None 

95 """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width.""" 

96 

97 min_width: Optional[int] = None 

98 """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None.""" 

99 

100 max_width: Optional[int] = None 

101 """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None.""" 

102 

103 ratio: Optional[int] = None 

104 """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents.""" 

105 

106 no_wrap: bool = False 

107 """bool: Prevent wrapping of text within the column. Defaults to ``False``.""" 

108 

109 _index: int = 0 

110 """Index of column.""" 

111 

112 _cells: List["RenderableType"] = field(default_factory=list) 

113 

114 def copy(self) -> "Column": 

115 """Return a copy of this Column.""" 

116 return replace(self, _cells=[]) 

117 

118 @property 

119 def cells(self) -> Iterable["RenderableType"]: 

120 """Get all cells in the column, not including header.""" 

121 yield from self._cells 

122 

123 @property 

124 def flexible(self) -> bool: 

125 """Check if this column is flexible.""" 

126 return self.ratio is not None 

127 

128 

129@dataclass 

130class Row: 

131 """Information regarding a row.""" 

132 

133 style: Optional[StyleType] = None 

134 """Style to apply to row.""" 

135 

136 end_section: bool = False 

137 """Indicated end of section, which will force a line beneath the row.""" 

138 

139 

140class _Cell(NamedTuple): 

141 """A single cell in a table.""" 

142 

143 style: StyleType 

144 """Style to apply to cell.""" 

145 renderable: "RenderableType" 

146 """Cell renderable.""" 

147 vertical: VerticalAlignMethod 

148 """Cell vertical alignment.""" 

149 

150 

151class Table(JupyterMixin): 

152 """A console renderable to draw a table. 

153 

154 Args: 

155 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. 

156 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. 

157 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. 

158 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. 

159 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. 

160 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. 

161 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. 

162 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). 

163 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. 

164 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. 

165 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. 

166 show_header (bool, optional): Show a header row. Defaults to True. 

167 show_footer (bool, optional): Show a footer row. Defaults to False. 

168 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. 

169 show_lines (bool, optional): Draw lines between every row. Defaults to False. 

170 leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. 

171 style (Union[str, Style], optional): Default style for the table. Defaults to "none". 

172 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. 

173 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". 

174 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". 

175 border_style (Union[str, Style], optional): Style of the border. Defaults to None. 

176 title_style (Union[str, Style], optional): Style of the title. Defaults to None. 

177 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. 

178 title_justify (str, optional): Justify method for title. Defaults to "center". 

179 caption_justify (str, optional): Justify method for caption. Defaults to "center". 

180 highlight (bool, optional): Highlight cell contents (if str). Defaults to False. 

181 """ 

182 

183 columns: List[Column] 

184 rows: List[Row] 

185 

186 def __init__( 

187 self, 

188 *headers: Union[Column, str], 

189 title: Optional[TextType] = None, 

190 caption: Optional[TextType] = None, 

191 width: Optional[int] = None, 

192 min_width: Optional[int] = None, 

193 box: Optional[box.Box] = box.HEAVY_HEAD, 

194 safe_box: Optional[bool] = None, 

195 padding: PaddingDimensions = (0, 1), 

196 collapse_padding: bool = False, 

197 pad_edge: bool = True, 

198 expand: bool = False, 

199 show_header: bool = True, 

200 show_footer: bool = False, 

201 show_edge: bool = True, 

202 show_lines: bool = False, 

203 leading: int = 0, 

204 style: StyleType = "none", 

205 row_styles: Optional[Iterable[StyleType]] = None, 

206 header_style: Optional[StyleType] = "table.header", 

207 footer_style: Optional[StyleType] = "table.footer", 

208 border_style: Optional[StyleType] = None, 

209 title_style: Optional[StyleType] = None, 

210 caption_style: Optional[StyleType] = None, 

211 title_justify: "JustifyMethod" = "center", 

212 caption_justify: "JustifyMethod" = "center", 

213 highlight: bool = False, 

214 ) -> None: 

215 

216 self.columns: List[Column] = [] 

217 self.rows: List[Row] = [] 

218 self.title = title 

219 self.caption = caption 

220 self.width = width 

221 self.min_width = min_width 

222 self.box = box 

223 self.safe_box = safe_box 

224 self._padding = Padding.unpack(padding) 

225 self.pad_edge = pad_edge 

226 self._expand = expand 

227 self.show_header = show_header 

228 self.show_footer = show_footer 

229 self.show_edge = show_edge 

230 self.show_lines = show_lines 

231 self.leading = leading 

232 self.collapse_padding = collapse_padding 

233 self.style = style 

234 self.header_style = header_style or "" 

235 self.footer_style = footer_style or "" 

236 self.border_style = border_style 

237 self.title_style = title_style 

238 self.caption_style = caption_style 

239 self.title_justify: "JustifyMethod" = title_justify 

240 self.caption_justify: "JustifyMethod" = caption_justify 

241 self.highlight = highlight 

242 self.row_styles: Sequence[StyleType] = list(row_styles or []) 

243 append_column = self.columns.append 

244 for header in headers: 

245 if isinstance(header, str): 

246 self.add_column(header=header) 

247 else: 

248 header._index = len(self.columns) 

249 append_column(header) 

250 

251 @classmethod 

252 def grid( 

253 cls, 

254 *headers: Union[Column, str], 

255 padding: PaddingDimensions = 0, 

256 collapse_padding: bool = True, 

257 pad_edge: bool = False, 

258 expand: bool = False, 

259 ) -> "Table": 

260 """Get a table with no lines, headers, or footer. 

261 

262 Args: 

263 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. 

264 padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0. 

265 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True. 

266 pad_edge (bool, optional): Enable padding around edges of table. Defaults to False. 

267 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. 

268 

269 Returns: 

270 Table: A table instance. 

271 """ 

272 return cls( 

273 *headers, 

274 box=None, 

275 padding=padding, 

276 collapse_padding=collapse_padding, 

277 show_header=False, 

278 show_footer=False, 

279 show_edge=False, 

280 pad_edge=pad_edge, 

281 expand=expand, 

282 ) 

283 

284 @property 

285 def expand(self) -> bool: 

286 """Setting a non-None self.width implies expand.""" 

287 return self._expand or self.width is not None 

288 

289 @expand.setter 

290 def expand(self, expand: bool) -> None: 

291 """Set expand.""" 

292 self._expand = expand 

293 

294 @property 

295 def _extra_width(self) -> int: 

296 """Get extra width to add to cell content.""" 

297 width = 0 

298 if self.box and self.show_edge: 

299 width += 2 

300 if self.box: 

301 width += len(self.columns) - 1 

302 return width 

303 

304 @property 

305 def row_count(self) -> int: 

306 """Get the current number of rows.""" 

307 return len(self.rows) 

308 

309 def get_row_style(self, console: "Console", index: int) -> StyleType: 

310 """Get the current row style.""" 

311 style = Style.null() 

312 if self.row_styles: 

313 style += console.get_style(self.row_styles[index % len(self.row_styles)]) 

314 row_style = self.rows[index].style 

315 if row_style is not None: 

316 style += console.get_style(row_style) 

317 return style 

318 

319 def __rich_measure__( 

320 self, console: "Console", options: "ConsoleOptions" 

321 ) -> Measurement: 

322 max_width = options.max_width 

323 if self.width is not None: 

324 max_width = self.width 

325 if max_width < 0: 

326 return Measurement(0, 0) 

327 

328 extra_width = self._extra_width 

329 max_width = sum( 

330 self._calculate_column_widths( 

331 console, options.update_width(max_width - extra_width) 

332 ) 

333 ) 

334 _measure_column = self._measure_column 

335 

336 measurements = [ 

337 _measure_column(console, options.update_width(max_width), column) 

338 for column in self.columns 

339 ] 

340 minimum_width = ( 

341 sum(measurement.minimum for measurement in measurements) + extra_width 

342 ) 

343 maximum_width = ( 

344 sum(measurement.maximum for measurement in measurements) + extra_width 

345 if (self.width is None) 

346 else self.width 

347 ) 

348 measurement = Measurement(minimum_width, maximum_width) 

349 measurement = measurement.clamp(self.min_width) 

350 return measurement 

351 

352 @property 

353 def padding(self) -> Tuple[int, int, int, int]: 

354 """Get cell padding.""" 

355 return self._padding 

356 

357 @padding.setter 

358 def padding(self, padding: PaddingDimensions) -> "Table": 

359 """Set cell padding.""" 

360 self._padding = Padding.unpack(padding) 

361 return self 

362 

363 def add_column( 

364 self, 

365 header: "RenderableType" = "", 

366 footer: "RenderableType" = "", 

367 *, 

368 header_style: Optional[StyleType] = None, 

369 footer_style: Optional[StyleType] = None, 

370 style: Optional[StyleType] = None, 

371 justify: "JustifyMethod" = "left", 

372 vertical: "VerticalAlignMethod" = "top", 

373 overflow: "OverflowMethod" = "ellipsis", 

374 width: Optional[int] = None, 

375 min_width: Optional[int] = None, 

376 max_width: Optional[int] = None, 

377 ratio: Optional[int] = None, 

378 no_wrap: bool = False, 

379 ) -> None: 

380 """Add a column to the table. 

381 

382 Args: 

383 header (RenderableType, optional): Text or renderable for the header. 

384 Defaults to "". 

385 footer (RenderableType, optional): Text or renderable for the footer. 

386 Defaults to "". 

387 header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None. 

388 footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None. 

389 style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None. 

390 justify (JustifyMethod, optional): Alignment for cells. Defaults to "left". 

391 vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top". 

392 overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis". 

393 width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None. 

394 min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None. 

395 max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None. 

396 ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None. 

397 no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column. 

398 """ 

399 

400 column = Column( 

401 _index=len(self.columns), 

402 header=header, 

403 footer=footer, 

404 header_style=header_style or "", 

405 footer_style=footer_style or "", 

406 style=style or "", 

407 justify=justify, 

408 vertical=vertical, 

409 overflow=overflow, 

410 width=width, 

411 min_width=min_width, 

412 max_width=max_width, 

413 ratio=ratio, 

414 no_wrap=no_wrap, 

415 ) 

416 self.columns.append(column) 

417 

418 def add_row( 

419 self, 

420 *renderables: Optional["RenderableType"], 

421 style: Optional[StyleType] = None, 

422 end_section: bool = False, 

423 ) -> None: 

424 """Add a row of renderables. 

425 

426 Args: 

427 *renderables (None or renderable): Each cell in a row must be a renderable object (including str), 

428 or ``None`` for a blank cell. 

429 style (StyleType, optional): An optional style to apply to the entire row. Defaults to None. 

430 end_section (bool, optional): End a section and draw a line. Defaults to False. 

431 

432 Raises: 

433 errors.NotRenderableError: If you add something that can't be rendered. 

434 """ 

435 

436 def add_cell(column: Column, renderable: "RenderableType") -> None: 

437 column._cells.append(renderable) 

438 

439 cell_renderables: List[Optional["RenderableType"]] = list(renderables) 

440 

441 columns = self.columns 

442 if len(cell_renderables) < len(columns): 

443 cell_renderables = [ 

444 *cell_renderables, 

445 *[None] * (len(columns) - len(cell_renderables)), 

446 ] 

447 for index, renderable in enumerate(cell_renderables): 

448 if index == len(columns): 

449 column = Column(_index=index) 

450 for _ in self.rows: 

451 add_cell(column, Text("")) 

452 self.columns.append(column) 

453 else: 

454 column = columns[index] 

455 if renderable is None: 

456 add_cell(column, "") 

457 elif is_renderable(renderable): 

458 add_cell(column, renderable) 

459 else: 

460 raise errors.NotRenderableError( 

461 f"unable to render {type(renderable).__name__}; a string or other renderable object is required" 

462 ) 

463 self.rows.append(Row(style=style, end_section=end_section)) 

464 

465 def add_section(self) -> None: 

466 """Add a new section (draw a line after current row).""" 

467 

468 if self.rows: 

469 self.rows[-1].end_section = True 

470 

471 def __rich_console__( 

472 self, console: "Console", options: "ConsoleOptions" 

473 ) -> "RenderResult": 

474 

475 if not self.columns: 

476 yield Segment("\n") 

477 return 

478 

479 max_width = options.max_width 

480 if self.width is not None: 

481 max_width = self.width 

482 

483 extra_width = self._extra_width 

484 widths = self._calculate_column_widths( 

485 console, options.update_width(max_width - extra_width) 

486 ) 

487 table_width = sum(widths) + extra_width 

488 

489 render_options = options.update( 

490 width=table_width, highlight=self.highlight, height=None 

491 ) 

492 

493 def render_annotation( 

494 text: TextType, style: StyleType, justify: "JustifyMethod" = "center" 

495 ) -> "RenderResult": 

496 render_text = ( 

497 console.render_str(text, style=style, highlight=False) 

498 if isinstance(text, str) 

499 else text 

500 ) 

501 return console.render( 

502 render_text, options=render_options.update(justify=justify) 

503 ) 

504 

505 if self.title: 

506 yield from render_annotation( 

507 self.title, 

508 style=Style.pick_first(self.title_style, "table.title"), 

509 justify=self.title_justify, 

510 ) 

511 yield from self._render(console, render_options, widths) 

512 if self.caption: 

513 yield from render_annotation( 

514 self.caption, 

515 style=Style.pick_first(self.caption_style, "table.caption"), 

516 justify=self.caption_justify, 

517 ) 

518 

519 def _calculate_column_widths( 

520 self, console: "Console", options: "ConsoleOptions" 

521 ) -> List[int]: 

522 """Calculate the widths of each column, including padding, not including borders.""" 

523 max_width = options.max_width 

524 columns = self.columns 

525 width_ranges = [ 

526 self._measure_column(console, options, column) for column in columns 

527 ] 

528 widths = [_range.maximum or 1 for _range in width_ranges] 

529 get_padding_width = self._get_padding_width 

530 extra_width = self._extra_width 

531 if self.expand: 

532 ratios = [col.ratio or 0 for col in columns if col.flexible] 

533 if any(ratios): 

534 fixed_widths = [ 

535 0 if column.flexible else _range.maximum 

536 for _range, column in zip(width_ranges, columns) 

537 ] 

538 flex_minimum = [ 

539 (column.width or 1) + get_padding_width(column._index) 

540 for column in columns 

541 if column.flexible 

542 ] 

543 flexible_width = max_width - sum(fixed_widths) 

544 flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum) 

545 iter_flex_widths = iter(flex_widths) 

546 for index, column in enumerate(columns): 

547 if column.flexible: 

548 widths[index] = fixed_widths[index] + next(iter_flex_widths) 

549 table_width = sum(widths) 

550 

551 if table_width > max_width: 

552 widths = self._collapse_widths( 

553 widths, 

554 [(column.width is None and not column.no_wrap) for column in columns], 

555 max_width, 

556 ) 

557 table_width = sum(widths) 

558 # last resort, reduce columns evenly 

559 if table_width > max_width: 

560 excess_width = table_width - max_width 

561 widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths) 

562 table_width = sum(widths) 

563 

564 width_ranges = [ 

565 self._measure_column(console, options.update_width(width), column) 

566 for width, column in zip(widths, columns) 

567 ] 

568 widths = [_range.maximum or 0 for _range in width_ranges] 

569 

570 if (table_width < max_width and self.expand) or ( 

571 self.min_width is not None and table_width < (self.min_width - extra_width) 

572 ): 

573 _max_width = ( 

574 max_width 

575 if self.min_width is None 

576 else min(self.min_width - extra_width, max_width) 

577 ) 

578 pad_widths = ratio_distribute(_max_width - table_width, widths) 

579 widths = [_width + pad for _width, pad in zip(widths, pad_widths)] 

580 

581 return widths 

582 

583 @classmethod 

584 def _collapse_widths( 

585 cls, widths: List[int], wrapable: List[bool], max_width: int 

586 ) -> List[int]: 

587 """Reduce widths so that the total is under max_width. 

588 

589 Args: 

590 widths (List[int]): List of widths. 

591 wrapable (List[bool]): List of booleans that indicate if a column may shrink. 

592 max_width (int): Maximum width to reduce to. 

593 

594 Returns: 

595 List[int]: A new list of widths. 

596 """ 

597 total_width = sum(widths) 

598 excess_width = total_width - max_width 

599 if any(wrapable): 

600 while total_width and excess_width > 0: 

601 max_column = max( 

602 width for width, allow_wrap in zip(widths, wrapable) if allow_wrap 

603 ) 

604 second_max_column = max( 

605 width if allow_wrap and width != max_column else 0 

606 for width, allow_wrap in zip(widths, wrapable) 

607 ) 

608 column_difference = max_column - second_max_column 

609 ratios = [ 

610 (1 if (width == max_column and allow_wrap) else 0) 

611 for width, allow_wrap in zip(widths, wrapable) 

612 ] 

613 if not any(ratios) or not column_difference: 

614 break 

615 max_reduce = [min(excess_width, column_difference)] * len(widths) 

616 widths = ratio_reduce(excess_width, ratios, max_reduce, widths) 

617 

618 total_width = sum(widths) 

619 excess_width = total_width - max_width 

620 return widths 

621 

622 def _get_cells( 

623 self, console: "Console", column_index: int, column: Column 

624 ) -> Iterable[_Cell]: 

625 """Get all the cells with padding and optional header.""" 

626 

627 collapse_padding = self.collapse_padding 

628 pad_edge = self.pad_edge 

629 padding = self.padding 

630 any_padding = any(padding) 

631 

632 first_column = column_index == 0 

633 last_column = column_index == len(self.columns) - 1 

634 

635 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {} 

636 

637 def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]: 

638 cached = _padding_cache.get((first_row, last_row)) 

639 if cached: 

640 return cached 

641 top, right, bottom, left = padding 

642 

643 if collapse_padding: 

644 if not first_column: 

645 left = max(0, left - right) 

646 if not last_row: 

647 bottom = max(0, top - bottom) 

648 

649 if not pad_edge: 

650 if first_column: 

651 left = 0 

652 if last_column: 

653 right = 0 

654 if first_row: 

655 top = 0 

656 if last_row: 

657 bottom = 0 

658 _padding = (top, right, bottom, left) 

659 _padding_cache[(first_row, last_row)] = _padding 

660 return _padding 

661 

662 raw_cells: List[Tuple[StyleType, "RenderableType"]] = [] 

663 _append = raw_cells.append 

664 get_style = console.get_style 

665 if self.show_header: 

666 header_style = get_style(self.header_style or "") + get_style( 

667 column.header_style 

668 ) 

669 _append((header_style, column.header)) 

670 cell_style = get_style(column.style or "") 

671 for cell in column.cells: 

672 _append((cell_style, cell)) 

673 if self.show_footer: 

674 footer_style = get_style(self.footer_style or "") + get_style( 

675 column.footer_style 

676 ) 

677 _append((footer_style, column.footer)) 

678 

679 if any_padding: 

680 _Padding = Padding 

681 for first, last, (style, renderable) in loop_first_last(raw_cells): 

682 yield _Cell( 

683 style, 

684 _Padding(renderable, get_padding(first, last)), 

685 getattr(renderable, "vertical", None) or column.vertical, 

686 ) 

687 else: 

688 for (style, renderable) in raw_cells: 

689 yield _Cell( 

690 style, 

691 renderable, 

692 getattr(renderable, "vertical", None) or column.vertical, 

693 ) 

694 

695 def _get_padding_width(self, column_index: int) -> int: 

696 """Get extra width from padding.""" 

697 _, pad_right, _, pad_left = self.padding 

698 if self.collapse_padding: 

699 if column_index > 0: 

700 pad_left = max(0, pad_left - pad_right) 

701 return pad_left + pad_right 

702 

703 def _measure_column( 

704 self, 

705 console: "Console", 

706 options: "ConsoleOptions", 

707 column: Column, 

708 ) -> Measurement: 

709 """Get the minimum and maximum width of the column.""" 

710 

711 max_width = options.max_width 

712 if max_width < 1: 

713 return Measurement(0, 0) 

714 

715 padding_width = self._get_padding_width(column._index) 

716 

717 if column.width is not None: 

718 # Fixed width column 

719 return Measurement( 

720 column.width + padding_width, column.width + padding_width 

721 ).with_maximum(max_width) 

722 # Flexible column, we need to measure contents 

723 min_widths: List[int] = [] 

724 max_widths: List[int] = [] 

725 append_min = min_widths.append 

726 append_max = max_widths.append 

727 get_render_width = Measurement.get 

728 for cell in self._get_cells(console, column._index, column): 

729 _min, _max = get_render_width(console, options, cell.renderable) 

730 append_min(_min) 

731 append_max(_max) 

732 

733 measurement = Measurement( 

734 max(min_widths) if min_widths else 1, 

735 max(max_widths) if max_widths else max_width, 

736 ).with_maximum(max_width) 

737 measurement = measurement.clamp( 

738 None if column.min_width is None else column.min_width + padding_width, 

739 None if column.max_width is None else column.max_width + padding_width, 

740 ) 

741 return measurement 

742 

743 def _render( 

744 self, console: "Console", options: "ConsoleOptions", widths: List[int] 

745 ) -> "RenderResult": 

746 table_style = console.get_style(self.style or "") 

747 

748 border_style = table_style + console.get_style(self.border_style or "") 

749 _column_cells = ( 

750 self._get_cells(console, column_index, column) 

751 for column_index, column in enumerate(self.columns) 

752 ) 

753 row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells)) 

754 _box = ( 

755 self.box.substitute( 

756 options, safe=pick_bool(self.safe_box, console.safe_box) 

757 ) 

758 if self.box 

759 else None 

760 ) 

761 _box = _box.get_plain_headed_box() if _box and not self.show_header else _box 

762 

763 new_line = Segment.line() 

764 

765 columns = self.columns 

766 show_header = self.show_header 

767 show_footer = self.show_footer 

768 show_edge = self.show_edge 

769 show_lines = self.show_lines 

770 leading = self.leading 

771 

772 _Segment = Segment 

773 if _box: 

774 box_segments = [ 

775 ( 

776 _Segment(_box.head_left, border_style), 

777 _Segment(_box.head_right, border_style), 

778 _Segment(_box.head_vertical, border_style), 

779 ), 

780 ( 

781 _Segment(_box.foot_left, border_style), 

782 _Segment(_box.foot_right, border_style), 

783 _Segment(_box.foot_vertical, border_style), 

784 ), 

785 ( 

786 _Segment(_box.mid_left, border_style), 

787 _Segment(_box.mid_right, border_style), 

788 _Segment(_box.mid_vertical, border_style), 

789 ), 

790 ] 

791 if show_edge: 

792 yield _Segment(_box.get_top(widths), border_style) 

793 yield new_line 

794 else: 

795 box_segments = [] 

796 

797 get_row_style = self.get_row_style 

798 get_style = console.get_style 

799 

800 for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)): 

801 header_row = first and show_header 

802 footer_row = last and show_footer 

803 row = ( 

804 self.rows[index - show_header] 

805 if (not header_row and not footer_row) 

806 else None 

807 ) 

808 max_height = 1 

809 cells: List[List[List[Segment]]] = [] 

810 if header_row or footer_row: 

811 row_style = Style.null() 

812 else: 

813 row_style = get_style( 

814 get_row_style(console, index - 1 if show_header else index) 

815 ) 

816 for width, cell, column in zip(widths, row_cell, columns): 

817 render_options = options.update( 

818 width=width, 

819 justify=column.justify, 

820 no_wrap=column.no_wrap, 

821 overflow=column.overflow, 

822 height=None, 

823 ) 

824 lines = console.render_lines( 

825 cell.renderable, 

826 render_options, 

827 style=get_style(cell.style) + row_style, 

828 ) 

829 max_height = max(max_height, len(lines)) 

830 cells.append(lines) 

831 

832 row_height = max(len(cell) for cell in cells) 

833 

834 def align_cell( 

835 cell: List[List[Segment]], 

836 vertical: "VerticalAlignMethod", 

837 width: int, 

838 style: Style, 

839 ) -> List[List[Segment]]: 

840 if header_row: 

841 vertical = "bottom" 

842 elif footer_row: 

843 vertical = "top" 

844 

845 if vertical == "top": 

846 return _Segment.align_top(cell, width, row_height, style) 

847 elif vertical == "middle": 

848 return _Segment.align_middle(cell, width, row_height, style) 

849 return _Segment.align_bottom(cell, width, row_height, style) 

850 

851 cells[:] = [ 

852 _Segment.set_shape( 

853 align_cell( 

854 cell, 

855 _cell.vertical, 

856 width, 

857 get_style(_cell.style) + row_style, 

858 ), 

859 width, 

860 max_height, 

861 ) 

862 for width, _cell, cell, column in zip(widths, row_cell, cells, columns) 

863 ] 

864 

865 if _box: 

866 if last and show_footer: 

867 yield _Segment( 

868 _box.get_row(widths, "foot", edge=show_edge), border_style 

869 ) 

870 yield new_line 

871 left, right, _divider = box_segments[0 if first else (2 if last else 1)] 

872 

873 # If the column divider is whitespace also style it with the row background 

874 divider = ( 

875 _divider 

876 if _divider.text.strip() 

877 else _Segment( 

878 _divider.text, row_style.background_style + _divider.style 

879 ) 

880 ) 

881 for line_no in range(max_height): 

882 if show_edge: 

883 yield left 

884 for last_cell, rendered_cell in loop_last(cells): 

885 yield from rendered_cell[line_no] 

886 if not last_cell: 

887 yield divider 

888 if show_edge: 

889 yield right 

890 yield new_line 

891 else: 

892 for line_no in range(max_height): 

893 for rendered_cell in cells: 

894 yield from rendered_cell[line_no] 

895 yield new_line 

896 if _box and first and show_header: 

897 yield _Segment( 

898 _box.get_row(widths, "head", edge=show_edge), border_style 

899 ) 

900 yield new_line 

901 end_section = row and row.end_section 

902 if _box and (show_lines or leading or end_section): 

903 if ( 

904 not last 

905 and not (show_footer and index >= len(row_cells) - 2) 

906 and not (show_header and header_row) 

907 ): 

908 if leading: 

909 yield _Segment( 

910 _box.get_row(widths, "mid", edge=show_edge) * leading, 

911 border_style, 

912 ) 

913 else: 

914 yield _Segment( 

915 _box.get_row(widths, "row", edge=show_edge), border_style 

916 ) 

917 yield new_line 

918 

919 if _box and show_edge: 

920 yield _Segment(_box.get_bottom(widths), border_style) 

921 yield new_line 

922 

923 

924if __name__ == "__main__": # pragma: no cover 

925 from rich.console import Console 

926 from rich.highlighter import ReprHighlighter 

927 from rich.table import Table as Table 

928 

929 from ._timer import timer 

930 

931 with timer("Table render"): 

932 table = Table( 

933 title="Star Wars Movies", 

934 caption="Rich example table", 

935 caption_justify="right", 

936 ) 

937 

938 table.add_column( 

939 "Released", header_style="bright_cyan", style="cyan", no_wrap=True 

940 ) 

941 table.add_column("Title", style="magenta") 

942 table.add_column("Box Office", justify="right", style="green") 

943 

944 table.add_row( 

945 "Dec 20, 2019", 

946 "Star Wars: The Rise of Skywalker", 

947 "$952,110,690", 

948 ) 

949 table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") 

950 table.add_row( 

951 "Dec 15, 2017", 

952 "Star Wars Ep. V111: The Last Jedi", 

953 "$1,332,539,889", 

954 style="on black", 

955 end_section=True, 

956 ) 

957 table.add_row( 

958 "Dec 16, 2016", 

959 "Rogue One: A Star Wars Story", 

960 "$1,332,439,889", 

961 ) 

962 

963 def header(text: str) -> None: 

964 console.print() 

965 console.rule(highlight(text)) 

966 console.print() 

967 

968 console = Console() 

969 highlight = ReprHighlighter() 

970 header("Example Table") 

971 console.print(table, justify="center") 

972 

973 table.expand = True 

974 header("expand=True") 

975 console.print(table) 

976 

977 table.width = 50 

978 header("width=50") 

979 

980 console.print(table, justify="center") 

981 

982 table.width = None 

983 table.expand = False 

984 table.row_styles = ["dim", "none"] 

985 header("row_styles=['dim', 'none']") 

986 

987 console.print(table, justify="center") 

988 

989 table.width = None 

990 table.expand = False 

991 table.row_styles = ["dim", "none"] 

992 table.leading = 1 

993 header("leading=1, row_styles=['dim', 'none']") 

994 console.print(table, justify="center") 

995 

996 table.width = None 

997 table.expand = False 

998 table.row_styles = ["dim", "none"] 

999 table.show_lines = True 

1000 table.leading = 0 

1001 header("show_lines=True, row_styles=['dim', 'none']") 

1002 console.print(table, justify="center")