1- from typing import List , Union
1+ from typing import List , Optional , Union
22from math import floor , ceil
33
44# constants
1010class 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 """
0 commit comments