diff options
Diffstat (limited to 'terminaltables3/build.py')
-rw-r--r-- | terminaltables3/build.py | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/terminaltables3/build.py b/terminaltables3/build.py new file mode 100644 index 0000000..c01349f --- /dev/null +++ b/terminaltables3/build.py @@ -0,0 +1,174 @@ +"""Combine cells into rows.""" + +from typing import Generator, Iterator, Optional, Sequence, Union + +from terminaltables3.width_and_alignment import visible_width + + +def combine( + line: Union[ + Generator[Union[int, str], None, None], Iterator[Optional[Union[int, str]]] + ], + left: str, + intersect: Optional[str], + right: str, +) -> Generator[int, None, None]: + """Zip borders between items in `line`. + + e.g. ('l', '1', 'c', '2', 'c', '3', 'r') + + :param iter line: List to iterate. + :param left: Left border. + :param intersect: Column separator. + :param right: Right border. + + :return: Yields combined objects. + """ + # Yield left border. + if left: + yield left + + # Yield items with intersect characters. + if intersect: + try: + for j, i in enumerate(line, start=-len(line) + 1): + yield i + if j: + yield intersect + except TypeError: # Generator. + try: + item = next(line) + except StopIteration: # Was empty all along. + pass + else: + while True: + yield item + try: + peek = next(line) + except StopIteration: + break + yield intersect + item = peek + else: + yield from line + + # Yield right border. + if right: + yield right + + +def build_border( + outer_widths: Sequence[int], + horizontal: str, + left: str, + intersect: str, + right: str, + title: Optional[str] = None, +): + """Build the top/bottom/middle row. Optionally embed the table title within the border. + + Title is hidden if it doesn't fit between the left/right characters/edges. + + Example return value: + ('<', '-----', '+', '------', '+', '-------', '>') + ('<', 'My Table', '----', '+', '------->') + + :param iter outer_widths: List of widths (with padding) for each column. + :param str horizontal: Character to stretch across each column. + :param str left: Left border. + :param str intersect: Column separator. + :param str right: Right border. + :param title: Overlay the title on the border between the left and right characters. + + :return: Returns a generator of strings representing a border. + :rtype: iter + """ + length = 0 + + # Hide title if it doesn't fit. + if title is not None and outer_widths: + try: + length = visible_width(title) + except TypeError: + title = str(title) + length = visible_width(title) + if length > sum(outer_widths) + len(intersect) * (len(outer_widths) - 1): + title = None + + # Handle no title. + if title is None or not outer_widths or not horizontal: + return combine((horizontal * c for c in outer_widths), left, intersect, right) + + # Handle title fitting in the first column. + if length == outer_widths[0]: + return combine( + [title] + [horizontal * c for c in outer_widths[1:]], left, intersect, right + ) + if length < outer_widths[0]: + columns = [title + horizontal * (outer_widths[0] - length)] + [ + horizontal * c for c in outer_widths[1:] + ] + return combine(columns, left, intersect, right) + + # Handle wide titles/narrow columns. + columns_and_intersects = [title] + for width in combine(outer_widths, None, bool(intersect), None): + # If title is taken care of. + if length < 1: + columns_and_intersects.append( + intersect if width is True else horizontal * width + ) + # If title's last character overrides an intersect character. + elif width is True and length == 1: + length = 0 + # If this is an intersect character that is overridden by the title. + elif width is True: + length -= 1 + # If title's last character is within a column. + elif width >= length: + columns_and_intersects[0] += horizontal * ( + width - length + ) # Append horizontal chars to title. + length = 0 + # If remainder of title won't fit in a column. + else: + length -= width + + return combine(columns_and_intersects, left, None, right) + + +def build_row(row, left, center, right): + """Combine single or multi-lined cells into a single row of list of lists including borders. + + Row must already be padded and extended so each cell has the same number of lines. + + Example return value: + [ + ['>', 'Left ', '|', 'Center', '|', 'Right', '<'], + ['>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'], + ] + + :param iter row: List of cells for one row. + :param str left: Left border. + :param str center: Column separator. + :param str right: Right border. + + :return: Yields other generators that yield strings. + :rtype: iter + """ + if not row or not row[0]: + yield combine((), left, center, right) + return + for row_index in range(len(row[0])): + yield combine((c[row_index] for c in row), left, center, right) + + +def flatten(table): + """Flatten table data into a single string with newlines. + + :param iter table: Padded and bordered table data. + + :return: Joined rows/cells. + :rtype: str + """ + return "\n".join("".join(r) for r in table) |