Skip to content

Commit 0cda863

Browse files
committed
Made header, body, footer optional
1 parent f615afc commit 0cda863

File tree

2 files changed

+195
-81
lines changed

2 files changed

+195
-81
lines changed

table2ascii/__init__.py

Lines changed: 107 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Union
1+
from typing import List, Optional, Union
22
from math import floor, ceil
33

44
# constants
@@ -10,57 +10,74 @@
1010
class TableToAscii:
1111
"""Class used to convert a 2D Python table to ASCII text"""
1212

13-
def __init__(self, header_row: List, body: List[List], footer_row: List):
13+
def __init__(
14+
self,
15+
header_row: Optional[List],
16+
body: Optional[List[List]],
17+
footer_row: Optional[List],
18+
):
1419
"""Validate arguments and initialize fields"""
15-
# check that values are valid
16-
if len(header_row) != len(footer_row):
17-
raise ValueError("header row and footer row must have the same length")
18-
if len(header_row) != len(body[0]):
19-
raise ValueError("header row and body rows must have the same length")
20-
for row in body[1:]:
21-
if len(body[0]) != len(row):
22-
raise ValueError("all rows in body must have the same length")
20+
# check if columns in header are different from footer
21+
if header_row and footer_row and len(header_row) != len(footer_row):
22+
raise ValueError("Header row and footer row must have the same length")
23+
# check if columns in header are different from body
24+
if header_row and body and len(body) > 0 and len(header_row) != len(body[0]):
25+
raise ValueError("Header row and body rows must have the same length")
26+
# 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)):
28+
raise ValueError("All rows in body must have the same length")
2329

2430
# initialize fields
2531
self.__header_row = header_row
2632
self.__body = body
2733
self.__footer_row = footer_row
34+
self.__columns = self.__count_columns()
2835
self.__cell_widths = [5, 5, 5, 5, 5] # TODO: make this automatic
2936

3037
"""
31-
╔═════╦═══════════════════════╗ 0111112111113111113111113111114
32-
║ # ║ G H R S ║ 5 6 7 7 7 5
33-
╟─────╫───────────────────────╢ 899999a99999b99999b99999b99999c
34-
║ 1 ║ 30 40 35 30 ║ 5 6 7 7 7 5
35-
║ 2 ║ 30 40 35 30 ║ 5 6 7 7 7 5
36-
╟─────╫───────────────────────╢ deeeeefeeeeegeeeeegeeeeegeeeeeh
37-
║ SUM ║ 130 140 135 130 ║ 5 6 7 7 7 5
38-
╚═════╩═══════════════════════╝ i11111j11111k11111k11111k11111l
38+
╔═════╦═══════════════════════╗ ABBBBBCBBBBBDBBBBBDBBBBBDBBBBBE
39+
║ # ║ G H R S ║ F G H H H F
40+
╟─────╫───────────────────────╢ IJJJJJKJJJJJLJJJJJLJJJJJLJJJJJM
41+
║ 1 ║ 30 40 35 30 ║ F G H H H F
42+
║ 2 ║ 30 40 35 30 ║ F G H H H F
43+
╟─────╫───────────────────────╢ NOOOOOPOOOOOQOOOOOQOOOOOQOOOOOR
44+
║ SUM ║ 130 140 135 130 ║ F G H H H F
45+
╚═════╩═══════════════════════╝ SBBBBBTBBBBBUBBBBBUBBBBBUBBBBBV
3946
"""
40-
self.parts = {
41-
"top_left_corner": "╔", # 0
42-
"top_and_bottom_edge": "═", # 1
43-
"first_col_top_tee": "╦", # 2
44-
"top_tee": "═", # 3
45-
"top_right_corner": "╗", # 4
46-
"left_and_right_edge": "║", # 5
47-
"first_col_sep": "║", # 6
48-
"middle_edge": " ", # 7
49-
"header_left_tee": "╟", # 8
50-
"header_row_sep": "─", # 9
51-
"first_col_header_cross": "╫", # a
52-
"header_row_cross": "─", # b
53-
"right_tee": "╢", # c
54-
"footer_left_tee": "╟", # d
55-
"footer_row_sep": "─", # e
56-
"first_col_footer_cross": "╫", # f
57-
"footer_row_cross": "─", # g
58-
"bottom_left_corner": "╚", # i
59-
"first_col_bottom_tee": "╩", # j
60-
"bottom_tee": "═", # k
61-
"bottom_right_corner": "╝", # l
47+
self.__parts = {
48+
"top_left_corner": "╔", # A
49+
"top_and_bottom_edge": "═", # B
50+
"first_col_top_tee": "╦", # C
51+
"top_tee": "═", # D
52+
"top_right_corner": "╗", # E
53+
"left_and_right_edge": "║", # F
54+
"first_col_sep": "║", # G
55+
"middle_edge": " ", # H
56+
"header_left_tee": "╟", # I
57+
"header_row_sep": "─", # J
58+
"first_col_header_cross": "╫", # K
59+
"header_row_cross": "─", # L
60+
"header_right_tee": "╢", # M
61+
"footer_left_tee": "╟", # N
62+
"footer_row_sep": "─", # O
63+
"first_col_footer_cross": "╫", # P
64+
"footer_row_cross": "─", # Q
65+
"footer_right_tee": "╢", # R
66+
"bottom_left_corner": "╚", # S
67+
"first_col_bottom_tee": "╩", # T
68+
"bottom_tee": "═", # U
69+
"bottom_right_corner": "╝", # V
6270
}
6371

72+
def __count_columns(self):
73+
if self.__header_row:
74+
return len(self.__header_row)
75+
if self.__footer_row:
76+
return len(self.__footer_row)
77+
if self.__body and len(self.__body) > 0:
78+
return len(self.__body[0])
79+
return 0
80+
6481
def __pad(self, text: str, width: int, alignment: int = ALIGN_CENTER):
6582
"""Pad a string of text to a given width with specified alignment"""
6683
if alignment == ALIGN_LEFT:
@@ -95,7 +112,7 @@ def __row_to_ascii(
95112
# separation of first column from the rest of the table
96113
output += first_col_sep
97114
# add remaining columns
98-
for i in range(1, len(self.__header_row)):
115+
for i in range(1, self.__columns):
99116
# content between separators
100117
output += (
101118
# edge or row separator if filler is specified
@@ -113,90 +130,100 @@ def __row_to_ascii(
113130
def __top_edge_to_ascii(self) -> str:
114131
"""Assembles the top edge of the ascii table"""
115132
return self.__row_to_ascii(
116-
left=self.parts["top_left_corner"],
117-
first_col_sep=self.parts["first_col_top_tee"],
118-
col_sep=self.parts["top_tee"],
119-
right=self.parts["top_right_corner"],
120-
filler=self.parts["top_and_bottom_edge"],
133+
left=self.__parts["top_left_corner"],
134+
first_col_sep=self.__parts["first_col_top_tee"],
135+
col_sep=self.__parts["top_tee"],
136+
right=self.__parts["top_right_corner"],
137+
filler=self.__parts["top_and_bottom_edge"],
121138
)
122139

123140
def __bottom_edge_to_ascii(self) -> str:
124141
"""Assembles the top edge of the ascii table"""
125142
return self.__row_to_ascii(
126-
left=self.parts["bottom_left_corner"],
127-
first_col_sep=self.parts["first_col_bottom_tee"],
128-
col_sep=self.parts["bottom_tee"],
129-
right=self.parts["bottom_right_corner"],
130-
filler=self.parts["top_and_bottom_edge"],
143+
left=self.__parts["bottom_left_corner"],
144+
first_col_sep=self.__parts["first_col_bottom_tee"],
145+
col_sep=self.__parts["bottom_tee"],
146+
right=self.__parts["bottom_right_corner"],
147+
filler=self.__parts["top_and_bottom_edge"],
131148
)
132149

133150
def __header_row_to_ascii(self) -> str:
134151
"""Assembles the header row line of the ascii table"""
135152
return self.__row_to_ascii(
136-
left=self.parts["left_and_right_edge"],
137-
first_col_sep=self.parts["first_col_sep"],
138-
col_sep=self.parts["middle_edge"],
139-
right=self.parts["left_and_right_edge"],
153+
left=self.__parts["left_and_right_edge"],
154+
first_col_sep=self.__parts["first_col_sep"],
155+
col_sep=self.__parts["middle_edge"],
156+
right=self.__parts["left_and_right_edge"],
140157
filler=self.__header_row,
141158
)
142159

143160
def __footer_row_to_ascii(self) -> str:
144161
"""Assembles the header row line of the ascii table"""
145162
return self.__row_to_ascii(
146-
left=self.parts["left_and_right_edge"],
147-
first_col_sep=self.parts["first_col_sep"],
148-
col_sep=self.parts["middle_edge"],
149-
right=self.parts["left_and_right_edge"],
163+
left=self.__parts["left_and_right_edge"],
164+
first_col_sep=self.__parts["first_col_sep"],
165+
col_sep=self.__parts["middle_edge"],
166+
right=self.__parts["left_and_right_edge"],
150167
filler=self.__footer_row,
151168
)
152169

153170
def __header_sep_to_ascii(self) -> str:
154171
"""Assembles the seperator below the header of the ascii table"""
155172
return self.__row_to_ascii(
156-
left=self.parts["header_left_tee"],
157-
first_col_sep=self.parts["first_col_header_cross"],
158-
col_sep=self.parts["header_row_cross"],
159-
right=self.parts["right_tee"],
160-
filler=self.parts["header_row_sep"],
173+
left=self.__parts["header_left_tee"],
174+
first_col_sep=self.__parts["first_col_header_cross"],
175+
col_sep=self.__parts["header_row_cross"],
176+
right=self.__parts["header_right_tee"],
177+
filler=self.__parts["header_row_sep"],
161178
)
162179

163180
def __footer_sep_to_ascii(self) -> str:
164181
"""Assembles the seperator below the header of the ascii table"""
165182
return self.__row_to_ascii(
166-
left=self.parts["footer_left_tee"],
167-
first_col_sep=self.parts["first_col_footer_cross"],
168-
col_sep=self.parts["footer_row_cross"],
169-
right=self.parts["right_tee"],
170-
filler=self.parts["header_row_sep"],
183+
left=self.__parts["footer_left_tee"],
184+
first_col_sep=self.__parts["first_col_footer_cross"],
185+
col_sep=self.__parts["footer_row_cross"],
186+
right=self.__parts["footer_right_tee"],
187+
filler=self.__parts["footer_row_sep"],
171188
)
172189

173190
def __body_to_ascii(self) -> str:
174191
output: str = ""
175192
for row in self.__body:
176193
output += self.__row_to_ascii(
177-
left=self.parts["left_and_right_edge"],
178-
first_col_sep=self.parts["first_col_sep"],
179-
col_sep=self.parts["middle_edge"],
180-
right=self.parts["left_and_right_edge"],
194+
left=self.__parts["left_and_right_edge"],
195+
first_col_sep=self.__parts["first_col_sep"],
196+
col_sep=self.__parts["middle_edge"],
197+
right=self.__parts["left_and_right_edge"],
181198
filler=row,
182199
)
183200
return output
184201

185202
def to_ascii(self) -> str:
203+
# top row of table
204+
table = self.__top_edge_to_ascii()
186205
# add table header
187-
table: str = self.__top_edge_to_ascii()
188-
table += self.__header_row_to_ascii()
189-
table += self.__header_sep_to_ascii()
206+
if self.__header_row:
207+
table += self.__header_row_to_ascii()
208+
table += self.__header_sep_to_ascii()
190209
# add table body
191-
table += self.__body_to_ascii()
210+
if self.__body:
211+
table += self.__body_to_ascii()
192212
# add table footer
193-
table += self.__footer_sep_to_ascii()
194-
table += self.__footer_row_to_ascii()
213+
if self.__footer_row:
214+
table += self.__footer_sep_to_ascii()
215+
table += self.__footer_row_to_ascii()
216+
# bottom row of table
195217
table += self.__bottom_edge_to_ascii()
218+
# reurn ascii table
196219
return table
197220

198221

199-
def table2ascii(header_row: List, body: List[List], footer_row: List) -> str:
222+
def table2ascii(
223+
header_row: Optional[List] = None,
224+
body: Optional[List[List]] = None,
225+
footer_row: Optional[List] = None,
226+
) -> str:
200227
"""Convert a 2D Python table to ASCII text
201228
#TODO: add param documentation
202229
"""

tests/test_convert.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from table2ascii import table2ascii as t2a
22

33

4-
def test_normal():
4+
def test_header_body_footer():
55
text = t2a(
66
header_row=["#", "G", "H", "R", "S"],
77
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
@@ -18,3 +18,90 @@ def test_normal():
1818
"╚═════╩═══════════════════════╝\n"
1919
)
2020
assert text == expected
21+
22+
23+
def test_body_footer():
24+
text = t2a(
25+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
26+
footer_row=["SUM", "130", "140", "135", "130"],
27+
)
28+
expected = (
29+
"╔═════╦═══════════════════════╗\n"
30+
"║ 1 ║ 30 40 35 30 ║\n"
31+
"║ 2 ║ 30 40 35 30 ║\n"
32+
"╟─────╫───────────────────────╢\n"
33+
"║ SUM ║ 130 140 135 130 ║\n"
34+
"╚═════╩═══════════════════════╝\n"
35+
)
36+
assert text == expected
37+
38+
39+
def test_header_body():
40+
text = t2a(
41+
header_row=["#", "G", "H", "R", "S"],
42+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
43+
)
44+
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"
51+
)
52+
assert text == expected
53+
54+
55+
def test_header_footer():
56+
text = t2a(
57+
header_row=["#", "G", "H", "R", "S"],
58+
footer_row=["SUM", "130", "140", "135", "130"],
59+
)
60+
expected = (
61+
"╔═════╦═══════════════════════╗\n"
62+
"║ # ║ G H R S ║\n"
63+
"╟─────╫───────────────────────╢\n"
64+
"╟─────╫───────────────────────╢\n"
65+
"║ SUM ║ 130 140 135 130 ║\n"
66+
"╚═════╩═══════════════════════╝\n"
67+
)
68+
assert text == expected
69+
70+
71+
def test_header():
72+
text = t2a(
73+
header_row=["#", "G", "H", "R", "S"],
74+
)
75+
expected = (
76+
"╔═════╦═══════════════════════╗\n"
77+
"║ # ║ G H R S ║\n"
78+
"╟─────╫───────────────────────╢\n"
79+
"╚═════╩═══════════════════════╝\n"
80+
)
81+
assert text == expected
82+
83+
84+
def test_body():
85+
text = t2a(
86+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
87+
)
88+
expected = (
89+
"╔═════╦═══════════════════════╗\n"
90+
"║ 1 ║ 30 40 35 30 ║\n"
91+
"║ 2 ║ 30 40 35 30 ║\n"
92+
"╚═════╩═══════════════════════╝\n"
93+
)
94+
assert text == expected
95+
96+
97+
def test_footer():
98+
text = t2a(
99+
footer_row=["SUM", "130", "140", "135", "130"],
100+
)
101+
expected = (
102+
"╔═════╦═══════════════════════╗\n"
103+
"╟─────╫───────────────────────╢\n"
104+
"║ SUM ║ 130 140 135 130 ║\n"
105+
"╚═════╩═══════════════════════╝\n"
106+
)
107+
assert text == expected

0 commit comments

Comments
 (0)