1
0
Fork 0
terminaltables3/terminaltables3/base_table.py

237 lines
9.9 KiB
Python
Raw Permalink Normal View History

"""Base table class. Define just the bare minimum to build tables."""
from typing import Generator, Optional, Sequence, Tuple
from terminaltables3.build import build_border, build_row, flatten
from terminaltables3.width_and_alignment import align_and_pad_cell, max_dimensions
class BaseTable:
"""Base table class.
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
:ivar str title: Optional title to show within the top border of the table.
:ivar bool inner_column_border: Separates columns.
:ivar bool inner_footing_row_border: Show a border before the last row.
:ivar bool inner_heading_row_border: Show a border after the first row.
:ivar bool inner_row_border: Show a border in between every row.
:ivar bool outer_border: Show the top, left, right, and bottom border.
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
:ivar int padding_left: Number of spaces to pad on the left side of every cell.
:ivar int padding_right: Number of spaces to pad on the right side of every cell.
"""
CHAR_F_INNER_HORIZONTAL = "-"
CHAR_F_INNER_INTERSECT = "+"
CHAR_F_INNER_VERTICAL = "|"
CHAR_F_OUTER_LEFT_INTERSECT = "+"
CHAR_F_OUTER_LEFT_VERTICAL = "|"
CHAR_F_OUTER_RIGHT_INTERSECT = "+"
CHAR_F_OUTER_RIGHT_VERTICAL = "|"
CHAR_H_INNER_HORIZONTAL = "-"
CHAR_H_INNER_INTERSECT = "+"
CHAR_H_INNER_VERTICAL = "|"
CHAR_H_OUTER_LEFT_INTERSECT = "+"
CHAR_H_OUTER_LEFT_VERTICAL = "|"
CHAR_H_OUTER_RIGHT_INTERSECT = "+"
CHAR_H_OUTER_RIGHT_VERTICAL = "|"
CHAR_INNER_HORIZONTAL = "-"
CHAR_INNER_INTERSECT = "+"
CHAR_INNER_VERTICAL = "|"
CHAR_OUTER_BOTTOM_HORIZONTAL = "-"
CHAR_OUTER_BOTTOM_INTERSECT = "+"
CHAR_OUTER_BOTTOM_LEFT = "+"
CHAR_OUTER_BOTTOM_RIGHT = "+"
CHAR_OUTER_LEFT_INTERSECT = "+"
CHAR_OUTER_LEFT_VERTICAL = "|"
CHAR_OUTER_RIGHT_INTERSECT = "+"
CHAR_OUTER_RIGHT_VERTICAL = "|"
CHAR_OUTER_TOP_HORIZONTAL = "-"
CHAR_OUTER_TOP_INTERSECT = "+"
CHAR_OUTER_TOP_LEFT = "+"
CHAR_OUTER_TOP_RIGHT = "+"
def __init__(
self, table_data: Sequence[Sequence[str]], title: Optional[str] = None
):
"""Constructor.
:param iter table_data: List (empty or list of lists of strings) representing the table.
:param title: Optional title to show within the top border of the table.
"""
self.table_data = table_data
self.title = title
self.inner_column_border = True
self.inner_footing_row_border = False
self.inner_heading_row_border = True
self.inner_row_border = False
self.outer_border = True
self.justify_columns = {} # {0: 'right', 1: 'left', 2: 'center'}
self.padding_left = 1
self.padding_right = 1
def horizontal_border(
self, style: str, outer_widths: Sequence[int]
) -> Tuple[str, ...]:
"""Build any kind of horizontal border for the table.
:param str style: Type of border to return.
:param iter outer_widths: List of widths (with padding) for each column.
:return: Prepared border as a tuple of strings.
:rtype: tuple
"""
if style == "top":
horizontal = self.CHAR_OUTER_TOP_HORIZONTAL
left = self.CHAR_OUTER_TOP_LEFT
intersect = (
self.CHAR_OUTER_TOP_INTERSECT if self.inner_column_border else ""
)
right = self.CHAR_OUTER_TOP_RIGHT
title = self.title
elif style == "bottom":
horizontal = self.CHAR_OUTER_BOTTOM_HORIZONTAL
left = self.CHAR_OUTER_BOTTOM_LEFT
intersect = (
self.CHAR_OUTER_BOTTOM_INTERSECT if self.inner_column_border else ""
)
right = self.CHAR_OUTER_BOTTOM_RIGHT
title = None
elif style == "heading":
horizontal = self.CHAR_H_INNER_HORIZONTAL
left = self.CHAR_H_OUTER_LEFT_INTERSECT if self.outer_border else ""
intersect = self.CHAR_H_INNER_INTERSECT if self.inner_column_border else ""
right = self.CHAR_H_OUTER_RIGHT_INTERSECT if self.outer_border else ""
title = None
elif style == "footing":
horizontal = self.CHAR_F_INNER_HORIZONTAL
left = self.CHAR_F_OUTER_LEFT_INTERSECT if self.outer_border else ""
intersect = self.CHAR_F_INNER_INTERSECT if self.inner_column_border else ""
right = self.CHAR_F_OUTER_RIGHT_INTERSECT if self.outer_border else ""
title = None
else:
horizontal = self.CHAR_INNER_HORIZONTAL
left = self.CHAR_OUTER_LEFT_INTERSECT if self.outer_border else ""
intersect = self.CHAR_INNER_INTERSECT if self.inner_column_border else ""
right = self.CHAR_OUTER_RIGHT_INTERSECT if self.outer_border else ""
title = None
return build_border(outer_widths, horizontal, left, intersect, right, title)
def gen_row_lines(
self, row: Sequence[str], style: str, inner_widths: Sequence[int], height: int
) -> Generator[Tuple[str, ...], None, None]:
r"""Combine cells in row and group them into lines with vertical borders.
Caller is expected to pass yielded lines to ''.join() to combine them into a printable line. Caller must append
newline character to the end of joined line.
In:
['Row One Column One', 'Two', 'Three']
Out:
[
('|', ' Row One Column One ', '|', ' Two ', '|', ' Three ', '|'),
]
In:
['Row One\nColumn One', 'Two', 'Three'],
Out:
[
('|', ' Row One ', '|', ' Two ', '|', ' Three ', '|'),
('|', ' Column One ', '|', ' ', '|', ' ', '|'),
]
:param iter row: One row in the table. List of cells.
:param str style: Type of border characters to use.
:param iter inner_widths: List of widths (no padding) for each column.
:param int height: Inner height (no padding) (number of lines) to expand row to.
:return: Yields lines split into components in a list. Caller must ''.join() line.
"""
cells_in_row = []
# Resize row if it doesn't have enough cells.
if len(row) != len(inner_widths):
row = row + [""] * (len(inner_widths) - len(row))
# Pad and align each cell. Split each cell into lines to support multi-line cells.
for i, cell in enumerate(row):
align = (self.justify_columns.get(i),)
inner_dimensions = (inner_widths[i], height)
padding = (self.padding_left, self.padding_right, 0, 0)
cells_in_row.append(
align_and_pad_cell(cell, align, inner_dimensions, padding)
)
# Determine border characters.
if style == "heading":
left = self.CHAR_H_OUTER_LEFT_VERTICAL if self.outer_border else ""
center = self.CHAR_H_INNER_VERTICAL if self.inner_column_border else ""
right = self.CHAR_H_OUTER_RIGHT_VERTICAL if self.outer_border else ""
elif style == "footing":
left = self.CHAR_F_OUTER_LEFT_VERTICAL if self.outer_border else ""
center = self.CHAR_F_INNER_VERTICAL if self.inner_column_border else ""
right = self.CHAR_F_OUTER_RIGHT_VERTICAL if self.outer_border else ""
else:
left = self.CHAR_OUTER_LEFT_VERTICAL if self.outer_border else ""
center = self.CHAR_INNER_VERTICAL if self.inner_column_border else ""
right = self.CHAR_OUTER_RIGHT_VERTICAL if self.outer_border else ""
# Yield each line.
yield from build_row(cells_in_row, left, center, right)
def gen_table(
self,
inner_widths: Sequence[int],
inner_heights: Sequence[int],
outer_widths: Sequence[int],
) -> Generator[Tuple[str, ...], None, None]:
"""Combine everything and yield every line of the entire table with borders.
:param iter inner_widths: List of widths (no padding) for each column.
:param iter inner_heights: List of heights (no padding) for each row.
:param iter outer_widths: List of widths (with padding) for each column.
:return:
"""
# Yield top border.
if self.outer_border:
yield self.horizontal_border("top", outer_widths)
# Yield table body.
row_count = len(self.table_data)
last_row_index, before_last_row_index = row_count - 1, row_count - 2
for i, row in enumerate(self.table_data):
# Yield the row line by line (e.g. multi-line rows).
if self.inner_heading_row_border and i == 0:
style = "heading"
elif self.inner_footing_row_border and i == last_row_index:
style = "footing"
else:
style = "row"
yield from self.gen_row_lines(row, style, inner_widths, inner_heights[i])
# If this is the last row then break. No separator needed.
if i == last_row_index:
break
# Yield heading separator.
if self.inner_heading_row_border and i == 0:
yield self.horizontal_border("heading", outer_widths)
# Yield footing separator.
elif self.inner_footing_row_border and i == before_last_row_index:
yield self.horizontal_border("footing", outer_widths)
# Yield row separator.
elif self.inner_row_border:
yield self.horizontal_border("row", outer_widths)
# Yield bottom border.
if self.outer_border:
yield self.horizontal_border("bottom", outer_widths)
@property
def table(self) -> str:
"""Return a large string of the entire table ready to be printed to the terminal."""
dimensions = max_dimensions(
self.table_data, self.padding_left, self.padding_right
)[:3]
return flatten(self.gen_table(*dimensions))