Skip to content

Commit b5b6edc

Browse files
committed
Automatically get column widths
1 parent 0cda863 commit b5b6edc

File tree

2 files changed

+76
-50
lines changed

2 files changed

+76
-50
lines changed

table2ascii/__init__.py

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ def __init__(
2424
if header_row and body and len(body) > 0 and len(header_row) != len(body[0]):
2525
raise ValueError("Header row and body rows must have the same length")
2626
# check if any rows in body have a different number of columns
27-
if body and len(body) and list(filter(lambda x: len(x) != len(body[0]), body)):
27+
if body and len(body) and tuple(filter(lambda r: len(r) != len(body[0]), body)):
2828
raise ValueError("All rows in body must have the same length")
2929

3030
# initialize fields
3131
self.__header_row = header_row
3232
self.__body = body
3333
self.__footer_row = footer_row
34-
self.__columns = self.__count_columns()
35-
self.__cell_widths = [5, 5, 5, 5, 5] # TODO: make this automatic
34+
self.__columns = self.count_columns()
35+
self.__cell_widths = self.get_column_widths()
3636

3737
"""
3838
╔═════╦═══════════════════════╗ ABBBBBCBBBBBDBBBBBDBBBBBDBBBBBE
@@ -69,7 +69,10 @@ def __init__(
6969
"bottom_right_corner": "╝", # V
7070
}
7171

72-
def __count_columns(self):
72+
def count_columns(self) -> int:
73+
"""Get the number of columns in the table
74+
based on the provided header, footer, and body lists.
75+
"""
7376
if self.__header_row:
7477
return len(self.__header_row)
7578
if self.__footer_row:
@@ -78,35 +81,54 @@ def __count_columns(self):
7881
return len(self.__body[0])
7982
return 0
8083

84+
def get_column_widths(self) -> List[int]:
85+
"""Get the minimum number of characters needed for the values
86+
in each column in the table with 1 space of padding on each side.
87+
"""
88+
col_counts = []
89+
for i in range(self.__columns):
90+
# number of characters in column of i of header, each body row, and footer
91+
header_size = len(self.__header_row[i]) if self.__header_row else 0
92+
body_size = (
93+
map(lambda row, i=i: len(row[i]), self.__body) if self.__body else [0]
94+
)
95+
footer_size = len(self.__footer_row[i]) if self.__footer_row else 0
96+
# get the max and add 2 for padding each side with a space
97+
col_counts.append(max(header_size, *body_size, footer_size) + 2)
98+
return col_counts
99+
81100
def __pad(self, text: str, width: int, alignment: int = ALIGN_CENTER):
82101
"""Pad a string of text to a given width with specified alignment"""
83102
if alignment == ALIGN_LEFT:
103+
# pad with spaces on the end
84104
return f" {text} " + (" " * (width - len(text) - 2))
85105
if alignment == ALIGN_CENTER:
106+
# pad with spaces, half on each side
86107
before = " " * ceil((width - len(text) - 2) / 2)
87108
after = " " * floor((width - len(text) - 2) / 2)
88109
return before + f" {text} " + after
89110
if alignment == ALIGN_RIGHT:
111+
# pad with spaces at the beginning
90112
return (" " * (width - len(text) - 2)) + f" {text} "
91113
raise ValueError(f"The value '{alignment}' is not valid for alignment.")
92114

93115
def __row_to_ascii(
94116
self,
95-
left: str,
117+
left_edge: str,
96118
first_col_sep: str,
97-
col_sep: str,
98-
right: str,
119+
column_seperator: str,
120+
right_edge: str,
99121
filler: Union[str, List],
100122
) -> str:
101123
"""Assembles a row of the ascii table"""
102124
# left edge of the row
103-
output: str = left
125+
output = left_edge
104126
# content across the first column
105127
output += (
106-
# edge or row separator if filler is specified
128+
# edge or row separator if filler is a specific character
107129
filler * self.__cell_widths[0]
108130
if isinstance(filler, str)
109-
# otherwise, first column content
131+
# otherwise, use the first column's content
110132
else self.__pad(str(filler[0]), self.__cell_widths[0])
111133
)
112134
# separation of first column from the rest of the table
@@ -115,86 +137,86 @@ def __row_to_ascii(
115137
for i in range(1, self.__columns):
116138
# content between separators
117139
output += (
118-
# edge or row separator if filler is specified
140+
# edge or row separator if filler is a specific character
119141
filler * self.__cell_widths[i]
120142
if isinstance(filler, str)
121-
# otherwise, column content
143+
# otherwise, use the column content
122144
else self.__pad(str(filler[i]), self.__cell_widths[i])
123145
)
124146
# add a separator
125-
output += col_sep
147+
output += column_seperator
126148
# replace last seperator with symbol for edge of the row
127-
output = output[0:-1] + right
149+
output = output[0:-1] + right_edge
128150
return output + "\n"
129151

130152
def __top_edge_to_ascii(self) -> str:
131153
"""Assembles the top edge of the ascii table"""
132154
return self.__row_to_ascii(
133-
left=self.__parts["top_left_corner"],
155+
left_edge=self.__parts["top_left_corner"],
134156
first_col_sep=self.__parts["first_col_top_tee"],
135-
col_sep=self.__parts["top_tee"],
136-
right=self.__parts["top_right_corner"],
157+
column_seperator=self.__parts["top_tee"],
158+
right_edge=self.__parts["top_right_corner"],
137159
filler=self.__parts["top_and_bottom_edge"],
138160
)
139161

140162
def __bottom_edge_to_ascii(self) -> str:
141163
"""Assembles the top edge of the ascii table"""
142164
return self.__row_to_ascii(
143-
left=self.__parts["bottom_left_corner"],
165+
left_edge=self.__parts["bottom_left_corner"],
144166
first_col_sep=self.__parts["first_col_bottom_tee"],
145-
col_sep=self.__parts["bottom_tee"],
146-
right=self.__parts["bottom_right_corner"],
167+
column_seperator=self.__parts["bottom_tee"],
168+
right_edge=self.__parts["bottom_right_corner"],
147169
filler=self.__parts["top_and_bottom_edge"],
148170
)
149171

150172
def __header_row_to_ascii(self) -> str:
151173
"""Assembles the header row line of the ascii table"""
152174
return self.__row_to_ascii(
153-
left=self.__parts["left_and_right_edge"],
175+
left_edge=self.__parts["left_and_right_edge"],
154176
first_col_sep=self.__parts["first_col_sep"],
155-
col_sep=self.__parts["middle_edge"],
156-
right=self.__parts["left_and_right_edge"],
177+
column_seperator=self.__parts["middle_edge"],
178+
right_edge=self.__parts["left_and_right_edge"],
157179
filler=self.__header_row,
158180
)
159181

160182
def __footer_row_to_ascii(self) -> str:
161183
"""Assembles the header row line of the ascii table"""
162184
return self.__row_to_ascii(
163-
left=self.__parts["left_and_right_edge"],
185+
left_edge=self.__parts["left_and_right_edge"],
164186
first_col_sep=self.__parts["first_col_sep"],
165-
col_sep=self.__parts["middle_edge"],
166-
right=self.__parts["left_and_right_edge"],
187+
column_seperator=self.__parts["middle_edge"],
188+
right_edge=self.__parts["left_and_right_edge"],
167189
filler=self.__footer_row,
168190
)
169191

170192
def __header_sep_to_ascii(self) -> str:
171193
"""Assembles the seperator below the header of the ascii table"""
172194
return self.__row_to_ascii(
173-
left=self.__parts["header_left_tee"],
195+
left_edge=self.__parts["header_left_tee"],
174196
first_col_sep=self.__parts["first_col_header_cross"],
175-
col_sep=self.__parts["header_row_cross"],
176-
right=self.__parts["header_right_tee"],
197+
column_seperator=self.__parts["header_row_cross"],
198+
right_edge=self.__parts["header_right_tee"],
177199
filler=self.__parts["header_row_sep"],
178200
)
179201

180202
def __footer_sep_to_ascii(self) -> str:
181203
"""Assembles the seperator below the header of the ascii table"""
182204
return self.__row_to_ascii(
183-
left=self.__parts["footer_left_tee"],
205+
left_edge=self.__parts["footer_left_tee"],
184206
first_col_sep=self.__parts["first_col_footer_cross"],
185-
col_sep=self.__parts["footer_row_cross"],
186-
right=self.__parts["footer_right_tee"],
207+
column_seperator=self.__parts["footer_row_cross"],
208+
right_edge=self.__parts["footer_right_tee"],
187209
filler=self.__parts["footer_row_sep"],
188210
)
189211

190212
def __body_to_ascii(self) -> str:
191213
output: str = ""
192214
for row in self.__body:
193215
output += self.__row_to_ascii(
194-
left=self.__parts["left_and_right_edge"],
216+
left_edge=self.__parts["left_and_right_edge"],
195217
first_col_sep=self.__parts["first_col_sep"],
196-
col_sep=self.__parts["middle_edge"],
197-
right=self.__parts["left_and_right_edge"],
218+
column_seperator=self.__parts["middle_edge"],
219+
right_edge=self.__parts["left_and_right_edge"],
198220
filler=row,
199221
)
200222
return output
@@ -225,6 +247,10 @@ def table2ascii(
225247
footer_row: Optional[List] = None,
226248
) -> str:
227249
"""Convert a 2D Python table to ASCII text
228-
#TODO: add param documentation
250+
251+
### Arguments
252+
:param header_row: :class:`Optional[List]` List of column values in the table's header row
253+
:param body: :class:`Optional[List[List]]` 2-dimensional list of values in the table's body
254+
:param footer_row: :class:`Optional[List]` List of column values in the table's footer row
229255
"""
230256
return TableToAscii(header_row, body, footer_row).to_ascii()

tests/test_convert.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ def test_header_body():
4242
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
4343
)
4444
expected = (
45-
"╔═════╦═══════════════════════╗\n"
46-
"║ # ║ G H R S \n"
47-
"╟─────╫───────────────────────╢\n"
48-
"║ 1 ║ 30 40 35 30 ║\n"
49-
"║ 2 ║ 30 40 35 30 ║\n"
50-
"╚═════╩═══════════════════════╝\n"
45+
"╔══════════════════════╗\n"
46+
"║ # ║ G H R S\n"
47+
"╟──────────────────────╢\n"
48+
"║ 1 ║ 30 40 35 30 ║\n"
49+
"║ 2 ║ 30 40 35 30 ║\n"
50+
"╚══════════════════════╝\n"
5151
)
5252
assert text == expected
5353

@@ -73,10 +73,10 @@ def test_header():
7373
header_row=["#", "G", "H", "R", "S"],
7474
)
7575
expected = (
76-
"╔═════╦═══════════════════════╗\n"
77-
"║ # ║ G H R S \n"
78-
"╟─────╫───────────────────────╢\n"
79-
"╚═════╩═══════════════════════╝\n"
76+
"╔══════════════════╗\n"
77+
"║ # ║ G H R S\n"
78+
"╟──────────────────╢\n"
79+
"╚══════════════════╝\n"
8080
)
8181
assert text == expected
8282

@@ -86,10 +86,10 @@ def test_body():
8686
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
8787
)
8888
expected = (
89-
"╔═════╦═══════════════════════╗\n"
90-
"║ 1 ║ 30 40 35 30 ║\n"
91-
"║ 2 ║ 30 40 35 30 ║\n"
92-
"╚═════╩═══════════════════════╝\n"
89+
"╔══════════════════════╗\n"
90+
"║ 1 ║ 30 40 35 30 ║\n"
91+
"║ 2 ║ 30 40 35 30 ║\n"
92+
"╚══════════════════════╝\n"
9393
)
9494
assert text == expected
9595

0 commit comments

Comments
 (0)