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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy