summaryrefslogtreecommitdiffstats
path: root/terminaltables/build.py
blob: 6b23b2f591538d9473303a3c18a8bfb0a74ebc73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""Combine cells into rows."""

from terminaltables.width_and_alignment import visible_width


def combine(line, left, intersect, right):
    """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:
        for i in line:
            yield i

    # Yield right border.
    if right:
        yield right


def build_border(outer_widths, horizontal, left, intersect, right, title=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)