Skip to content

Commit d0dcddc

Browse files
authored
feat: support for multiline cells (#30)
1 parent 9829e77 commit d0dcddc

File tree

3 files changed

+103
-34
lines changed

3 files changed

+103
-34
lines changed

table2ascii/table_to_ascii.py

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,22 @@ def __auto_column_widths(self) -> List[int]:
9090
Returns:
9191
List[int]: The minimum number of characters needed for each column
9292
"""
93+
94+
def longest_line(text: str) -> int:
95+
"""Returns the length of the longest line in a multi-line string"""
96+
return max(len(line) for line in text.splitlines()) if len(text) else 0
97+
9398
column_widths = []
99+
# get the width necessary for each column
94100
for i in range(self.__columns):
95101
# number of characters in column of i of header, each body row, and footer
96-
header_size = len(str(self.__header[i])) if self.__header else 0
102+
header_size = longest_line(str(self.__header[i])) if self.__header else 0
97103
body_size = (
98-
map(lambda row, i=i: len(str(row[i])), self.__body)
104+
map(lambda row, i=i: longest_line(str(row[i])), self.__body)
99105
if self.__body
100106
else [0]
101107
)
102-
footer_size = len(str(self.__footer[i])) if self.__footer else 0
108+
footer_size = longest_line(str(self.__footer[i])) if self.__footer else 0
103109
# get the max and add 2 for padding each side with a space
104110
column_widths.append(max(header_size, *body_size, footer_size) + 2)
105111
return column_widths
@@ -144,37 +150,51 @@ def __row_to_ascii(
144150
Returns:
145151
str: The line in the ascii table
146152
"""
147-
# left edge of the row
148-
output = left_edge
149-
# add columns
150-
for i in range(self.__columns):
151-
# content between separators
152-
output += (
153-
# edge or row separator if filler is a specific character
154-
filler * self.__column_widths[i]
155-
if isinstance(filler, str)
156-
# otherwise, use the column content
157-
else self.__pad(
158-
filler[i], self.__column_widths[i], self.__alignments[i]
159-
)
160-
)
161-
# column seperator
162-
sep = column_seperator
163-
if i == 0 and self.__first_col_heading:
164-
# use column heading if first column option is specified
165-
sep = heading_col_sep
166-
elif i == self.__columns - 2 and self.__last_col_heading:
167-
# use column heading if last column option is specified
168-
sep = heading_col_sep
169-
elif i == self.__columns - 1:
170-
# replace last seperator with symbol for edge of the row
171-
sep = right_edge
172-
output += sep
173-
# don't use separation row if it's only space
174-
if output.strip() == "":
175-
return ""
176-
# otherwise, return the row followed by newline
177-
return output + "\n"
153+
output = ""
154+
# find the maximum number of lines a single cell in the column has (minimum of 1)
155+
num_lines = max(len(str(cell).splitlines()) for cell in filler) or 1
156+
# repeat for each line of text in the cell
157+
for line_index in range(num_lines):
158+
# left edge of the row
159+
output += left_edge
160+
# add columns
161+
for col_index in range(self.__columns):
162+
# content between separators
163+
col_content = ""
164+
# if filler is a separator character, repeat it for the full width of the column
165+
if isinstance(filler, str):
166+
col_content = filler * self.__column_widths[col_index]
167+
# otherwise, use the text from the corresponding column in the filler list
168+
else:
169+
# get the text of the current line in the cell
170+
# if there are fewer lines in the current cell than others, empty string is used
171+
col_lines = str(filler[col_index]).splitlines()
172+
if line_index < len(col_lines):
173+
col_content = col_lines[line_index]
174+
# pad the text to the width of the column using the alignment
175+
col_content = self.__pad(
176+
col_content,
177+
self.__column_widths[col_index],
178+
self.__alignments[col_index],
179+
)
180+
output += col_content
181+
# column seperator
182+
sep = column_seperator
183+
if col_index == 0 and self.__first_col_heading:
184+
# use column heading if first column option is specified
185+
sep = heading_col_sep
186+
elif col_index == self.__columns - 2 and self.__last_col_heading:
187+
# use column heading if last column option is specified
188+
sep = heading_col_sep
189+
elif col_index == self.__columns - 1:
190+
# replace last seperator with symbol for edge of the row
191+
sep = right_edge
192+
output += sep
193+
output += "\n"
194+
# don't use separation row if it's only space
195+
if isinstance(filler, str) and output.strip() == "":
196+
output = ""
197+
return output
178198

179199
def __top_edge_to_ascii(self) -> str:
180200
"""

tests/test_alignments.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,28 @@ def test_alignment_numeric_data():
6565
"╚════╩══════════════════════╝"
6666
)
6767
assert text == expected
68+
69+
70+
def test_alignments_multiline_data():
71+
text = t2a(
72+
header=["Multiline\nHeader\nCell", "G", "Two\nLines", "R", "S"],
73+
body=[[1, "Alpha\nBeta\nGamma", 3, 4, "One\nTwo"]],
74+
footer=["A", "Footer\nBreak", 1, "Second\nCell\nBroken", 3],
75+
alignments=[Alignment.LEFT, Alignment.RIGHT, Alignment.CENTER, Alignment.LEFT, Alignment.CENTER],
76+
)
77+
expected = (
78+
"╔═══════════════════════════════════════════╗\n"
79+
"║ Multiline G Two R S ║\n"
80+
"║ Header Lines ║\n"
81+
"║ Cell ║\n"
82+
"╟───────────────────────────────────────────╢\n"
83+
"║ 1 Alpha 3 4 One ║\n"
84+
"║ Beta Two ║\n"
85+
"║ Gamma ║\n"
86+
"╟───────────────────────────────────────────╢\n"
87+
"║ A Footer 1 Second 3 ║\n"
88+
"║ Break Cell ║\n"
89+
"║ Broken ║\n"
90+
"╚═══════════════════════════════════════════╝"
91+
)
92+
assert text == expected

tests/test_convert.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,27 @@ def test_numeric_data():
199199
"╚════╩══════════════════════╝"
200200
)
201201
assert text == expected
202+
203+
204+
def test_multiline_cells():
205+
text = t2a(
206+
header=["Multiline\nHeader\nCell", "G", "Two\nLines", "R", "S"],
207+
body=[[1, "Alpha\nBeta\nGamma", 3, 4, "One\nTwo"]],
208+
footer=["A", "Footer\nBreak", 1, "Second\nCell\nBroken", 3],
209+
)
210+
expected = (
211+
"╔═══════════════════════════════════════════╗\n"
212+
"║ Multiline G Two R S ║\n"
213+
"║ Header Lines ║\n"
214+
"║ Cell ║\n"
215+
"╟───────────────────────────────────────────╢\n"
216+
"║ 1 Alpha 3 4 One ║\n"
217+
"║ Beta Two ║\n"
218+
"║ Gamma ║\n"
219+
"╟───────────────────────────────────────────╢\n"
220+
"║ A Footer 1 Second 3 ║\n"
221+
"║ Break Cell ║\n"
222+
"║ Broken ║\n"
223+
"╚═══════════════════════════════════════════╝"
224+
)
225+
assert text == expected

0 commit comments

Comments
 (0)