Skip to content

Commit 48d8882

Browse files
committed
Add column width option
1 parent ca41280 commit 48d8882

File tree

3 files changed

+87
-23
lines changed

3 files changed

+87
-23
lines changed

table2ascii/__init__.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,41 @@ def __init__(
2828
header: Optional[List],
2929
body: Optional[List[List]],
3030
footer: Optional[List],
31+
column_widths: Optional[List[int]],
3132
options: Options,
3233
):
3334
"""Validate arguments and initialize fields"""
34-
# check if columns in header are different from footer
35-
if header and footer and len(header) != len(footer):
36-
raise ValueError("Header row and footer row must have the same length")
37-
# check if columns in header are different from body
38-
if header and body and len(body) > 0 and len(header) != len(body[0]):
39-
raise ValueError("Header row and body rows must have the same length")
40-
# check if columns in header are different from body
41-
if footer and body and len(body) > 0 and len(footer) != len(body[0]):
42-
raise ValueError("Footer row and body rows must have the same length")
43-
# check if any rows in body have a different number of columns
44-
if body and len(body) and tuple(filter(lambda r: len(r) != len(body[0]), body)):
45-
raise ValueError("All rows in body must have the same length")
46-
4735
# initialize fields
4836
self.__header = header
4937
self.__body = body
5038
self.__footer = footer
5139
self.__options = options
5240
self.__columns = self.__count_columns()
53-
self.__cell_widths = self.__get_column_widths()
41+
42+
# check if footer has a different number of columns
43+
if footer and len(footer) != self.__columns:
44+
raise ValueError(
45+
"Footer must have the same number of columns as the other rows"
46+
)
47+
# check if any rows in body have a different number of columns
48+
if body and tuple(filter(lambda r: len(r) != self.__columns, body)):
49+
raise ValueError(
50+
"All rows in body must have the same number of columns as the other rows"
51+
)
52+
53+
# calculate column widths
54+
self.__column_widths = column_widths or self.__auto_column_widths()
55+
56+
# check if column widths specified have a different number of columns
57+
if column_widths and len(column_widths) != self.__columns:
58+
raise ValueError(
59+
"Length of `column_widths` list must equal the number of columns"
60+
)
61+
# check if column widths are not all at least 2
62+
if column_widths and min(column_widths) < 2:
63+
raise ValueError(
64+
"All values in `column_widths` must be greater than or equal to 2"
65+
)
5466

5567
"""
5668
╔═════╦═══════════════════════╗ ABBBBBCBBBBBDBBBBBDBBBBBDBBBBBE
@@ -99,11 +111,11 @@ def __count_columns(self) -> int:
99111
return len(self.__body[0])
100112
return 0
101113

102-
def __get_column_widths(self) -> List[int]:
114+
def __auto_column_widths(self) -> List[int]:
103115
"""Get the minimum number of characters needed for the values
104116
in each column in the table with 1 space of padding on each side.
105117
"""
106-
col_counts = []
118+
column_widths = []
107119
for i in range(self.__columns):
108120
# number of characters in column of i of header, each body row, and footer
109121
header_size = len(self.__header[i]) if self.__header else 0
@@ -112,8 +124,8 @@ def __get_column_widths(self) -> List[int]:
112124
)
113125
footer_size = len(self.__footer[i]) if self.__footer else 0
114126
# get the max and add 2 for padding each side with a space
115-
col_counts.append(max(header_size, *body_size, footer_size) + 2)
116-
return col_counts
127+
column_widths.append(max(header_size, *body_size, footer_size) + 2)
128+
return column_widths
117129

118130
def __pad(self, text: str, width: int, alignment: Alignment = Alignment.CENTER):
119131
"""Pad a string of text to a given width with specified alignment"""
@@ -148,10 +160,10 @@ def __row_to_ascii(
148160
# content between separators
149161
output += (
150162
# edge or row separator if filler is a specific character
151-
filler * self.__cell_widths[i]
163+
filler * self.__column_widths[i]
152164
if isinstance(filler, str)
153165
# otherwise, use the column content
154-
else self.__pad(str(filler[i]), self.__cell_widths[i])
166+
else self.__pad(str(filler[i]), self.__column_widths[i])
155167
)
156168
# column seperator
157169
sep = column_seperator
@@ -260,6 +272,7 @@ def table2ascii(
260272
header: Optional[List] = None,
261273
body: Optional[List[List]] = None,
262274
footer: Optional[List] = None,
275+
column_widths: Optional[List[int]] = None,
263276
**options,
264277
) -> str:
265278
"""Convert a 2D Python table to ASCII text
@@ -268,5 +281,9 @@ def table2ascii(
268281
:param header: :class:`Optional[List]` List of column values in the table's header row
269282
:param body: :class:`Optional[List[List]]` 2-dimensional list of values in the table's body
270283
:param footer: :class:`Optional[List]` List of column values in the table's footer row
284+
:param column_widths: :class:`Optional[List[int]]` List of widths in characters for each column (defaults to auto-sizing)
285+
:param footer: :class:`Optional[List]` List of column values in the table's footer row
271286
"""
272-
return TableToAscii(header, body, footer, Options(**options)).to_ascii()
287+
return TableToAscii(
288+
header, body, footer, column_widths, Options(**options)
289+
).to_ascii()

tests/test_column_widths.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from table2ascii import table2ascii as t2a
2+
3+
import pytest
4+
5+
6+
def test_column_widths():
7+
text = t2a(
8+
header=["#", "G", "H", "R", "S"],
9+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
10+
footer=["TOTL", "130", "140", "135", "130"],
11+
first_col_heading=True,
12+
last_col_heading=True,
13+
column_widths=[7, 5, 5, 5, 8],
14+
)
15+
expected = (
16+
"╔═══════╦═════════════════╦════════╗\n"
17+
"║ # ║ G H R ║ S ║\n"
18+
"╟───────╫─────────────────╫────────╢\n"
19+
"║ 1 ║ 30 40 35 ║ 30 ║\n"
20+
"║ 2 ║ 30 40 35 ║ 30 ║\n"
21+
"╟───────╫─────────────────╫────────╢\n"
22+
"║ TOTL ║ 130 140 135 ║ 130 ║\n"
23+
"╚═══════╩═════════════════╩════════╝\n"
24+
)
25+
assert text == expected
26+
27+
28+
def test_wrong_number_column_widths():
29+
with pytest.raises(ValueError):
30+
t2a(
31+
header=["#", "G", "H", "R", "S"],
32+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
33+
footer=["TOTL", "130", "140", "135", "130"],
34+
first_col_heading=True,
35+
last_col_heading=True,
36+
column_widths=[7, 5, 5, 5],
37+
)
38+
39+
40+
def test_negative_column_widths():
41+
with pytest.raises(ValueError):
42+
t2a(
43+
header=["#", "G", "H", "R", "S"],
44+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
45+
footer=["TOTL", "130", "140", "135", "130"],
46+
first_col_heading=True,
47+
last_col_heading=True,
48+
column_widths=[7, 5, 5, 5, -1],
49+
)

tests/test_heading_cols.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from table2ascii import table2ascii as t2a
22

3-
import pytest
4-
53

64
def test_first_column_heading():
75
text = t2a(

0 commit comments

Comments
 (0)