1- from typing import List , Optional , Union
2- from math import floor , ceil
31import enum
2+ from dataclasses import dataclass
3+ from math import ceil , floor
4+ from typing import List , Optional , Union
5+
6+
7+ @dataclass
8+ class Options :
9+ """Class for storing options that the user sets"""
410
11+ first_col_heading : bool = False
12+ last_col_heading : bool = False
13+
14+
15+ class Alignment (enum .Enum ):
16+ """Enum for alignment types"""
517
6- class Align (enum .Enum ):
718 LEFT = 0
819 RIGHT = 1
920 CENTER = 2
@@ -17,6 +28,7 @@ def __init__(
1728 header : Optional [List ],
1829 body : Optional [List [List ]],
1930 footer : Optional [List ],
31+ options : Options ,
2032 ):
2133 """Validate arguments and initialize fields"""
2234 # check if columns in header are different from footer
@@ -36,6 +48,7 @@ def __init__(
3648 self .__header = header
3749 self .__body = body
3850 self .__footer = footer
51+ self .__options = options
3952 self .__columns = self .__count_columns ()
4053 self .__cell_widths = self .__get_column_widths ()
4154
@@ -52,24 +65,24 @@ def __init__(
5265 self .__parts = {
5366 "top_left_corner" : "╔" , # A
5467 "top_and_bottom_edge" : "═" , # B
55- "first_col_top_tee " : "╦" , # C
68+ "heading_col_top_tee " : "╦" , # C
5669 "top_tee" : "═" , # D
5770 "top_right_corner" : "╗" , # E
5871 "left_and_right_edge" : "║" , # F
59- "first_col_sep " : "║" , # G
72+ "heading_col_sep " : "║" , # G
6073 "middle_edge" : " " , # H
6174 "header_left_tee" : "╟" , # I
6275 "header_row_sep" : "─" , # J
63- "first_col_header_cross " : "╫" , # K
76+ "heading_col_header_cross " : "╫" , # K
6477 "header_row_cross" : "─" , # L
6578 "header_right_tee" : "╢" , # M
6679 "footer_left_tee" : "╟" , # N
6780 "footer_row_sep" : "─" , # O
68- "first_col_footer_cross " : "╫" , # P
81+ "heading_col_footer_cross " : "╫" , # P
6982 "footer_row_cross" : "─" , # Q
7083 "footer_right_tee" : "╢" , # R
7184 "bottom_left_corner" : "╚" , # S
72- "first_col_bottom_tee " : "╩" , # T
85+ "heading_col_bottom_tee " : "╩" , # T
7386 "bottom_tee" : "═" , # U
7487 "bottom_right_corner" : "╝" , # V
7588 }
@@ -102,44 +115,36 @@ def __get_column_widths(self) -> List[int]:
102115 col_counts .append (max (header_size , * body_size , footer_size ) + 2 )
103116 return col_counts
104117
105- def __pad (self , text : str , width : int , alignment : Align = Align .CENTER ):
118+ def __pad (self , text : str , width : int , alignment : Alignment = Alignment .CENTER ):
106119 """Pad a string of text to a given width with specified alignment"""
107- if alignment == Align .LEFT :
120+ if alignment == Alignment .LEFT :
108121 # pad with spaces on the end
109122 return f" { text } " + (" " * (width - len (text ) - 2 ))
110- if alignment == Align .CENTER :
123+ if alignment == Alignment .CENTER :
111124 # pad with spaces, half on each side
112125 before = " " * floor ((width - len (text ) - 2 ) / 2 )
113126 after = " " * ceil ((width - len (text ) - 2 ) / 2 )
114127 return before + f" { text } " + after
115- if alignment == Align .RIGHT :
128+ if alignment == Alignment .RIGHT :
116129 # pad with spaces at the beginning
117130 return (" " * (width - len (text ) - 2 )) + f" { text } "
118131 raise ValueError (f"The value '{ alignment } ' is not valid for alignment." )
119132
120133 def __row_to_ascii (
121134 self ,
122135 left_edge : str ,
123- first_col_sep : str ,
136+ heading_col_sep : str ,
124137 column_seperator : str ,
125138 right_edge : str ,
126139 filler : Union [str , List ],
127140 ) -> str :
128141 """Assembles a row of the ascii table"""
142+ first_heading = self .__options .first_col_heading
143+ last_heading = self .__options .last_col_heading
129144 # left edge of the row
130145 output = left_edge
131- # content across the first column
132- output += (
133- # edge or row separator if filler is a specific character
134- filler * self .__cell_widths [0 ]
135- if isinstance (filler , str )
136- # otherwise, use the first column's content
137- else self .__pad (str (filler [0 ]), self .__cell_widths [0 ])
138- )
139- # separation of first column from the rest of the table
140- output += first_col_sep
141- # add remaining columns
142- for i in range (1 , self .__columns ):
146+ # add columns
147+ for i in range (self .__columns ):
143148 # content between separators
144149 output += (
145150 # edge or row separator if filler is a specific character
@@ -148,17 +153,22 @@ def __row_to_ascii(
148153 # otherwise, use the column content
149154 else self .__pad (str (filler [i ]), self .__cell_widths [i ])
150155 )
151- # add a separator
152- output += column_seperator
153- # replace last seperator with symbol for edge of the row
154- output = output [0 :- 1 ] + right_edge
156+ # column seperator
157+ sep = column_seperator
158+ if (i == 0 and first_heading ) or (i == self .__columns - 2 and last_heading ):
159+ # use column heading if option is specified
160+ sep = heading_col_sep
161+ elif i == self .__columns - 1 :
162+ # replace last seperator with symbol for edge of the row
163+ sep = right_edge
164+ output += sep
155165 return output + "\n "
156166
157167 def __top_edge_to_ascii (self ) -> str :
158168 """Assembles the top edge of the ascii table"""
159169 return self .__row_to_ascii (
160170 left_edge = self .__parts ["top_left_corner" ],
161- first_col_sep = self .__parts ["first_col_top_tee " ],
171+ heading_col_sep = self .__parts ["heading_col_top_tee " ],
162172 column_seperator = self .__parts ["top_tee" ],
163173 right_edge = self .__parts ["top_right_corner" ],
164174 filler = self .__parts ["top_and_bottom_edge" ],
@@ -168,7 +178,7 @@ def __bottom_edge_to_ascii(self) -> str:
168178 """Assembles the top edge of the ascii table"""
169179 return self .__row_to_ascii (
170180 left_edge = self .__parts ["bottom_left_corner" ],
171- first_col_sep = self .__parts ["first_col_bottom_tee " ],
181+ heading_col_sep = self .__parts ["heading_col_bottom_tee " ],
172182 column_seperator = self .__parts ["bottom_tee" ],
173183 right_edge = self .__parts ["bottom_right_corner" ],
174184 filler = self .__parts ["top_and_bottom_edge" ],
@@ -178,7 +188,7 @@ def __header_row_to_ascii(self) -> str:
178188 """Assembles the header row line of the ascii table"""
179189 return self .__row_to_ascii (
180190 left_edge = self .__parts ["left_and_right_edge" ],
181- first_col_sep = self .__parts ["first_col_sep " ],
191+ heading_col_sep = self .__parts ["heading_col_sep " ],
182192 column_seperator = self .__parts ["middle_edge" ],
183193 right_edge = self .__parts ["left_and_right_edge" ],
184194 filler = self .__header ,
@@ -188,7 +198,7 @@ def __footer_row_to_ascii(self) -> str:
188198 """Assembles the header row line of the ascii table"""
189199 return self .__row_to_ascii (
190200 left_edge = self .__parts ["left_and_right_edge" ],
191- first_col_sep = self .__parts ["first_col_sep " ],
201+ heading_col_sep = self .__parts ["heading_col_sep " ],
192202 column_seperator = self .__parts ["middle_edge" ],
193203 right_edge = self .__parts ["left_and_right_edge" ],
194204 filler = self .__footer ,
@@ -198,7 +208,7 @@ def __header_sep_to_ascii(self) -> str:
198208 """Assembles the seperator below the header of the ascii table"""
199209 return self .__row_to_ascii (
200210 left_edge = self .__parts ["header_left_tee" ],
201- first_col_sep = self .__parts ["first_col_header_cross " ],
211+ heading_col_sep = self .__parts ["heading_col_header_cross " ],
202212 column_seperator = self .__parts ["header_row_cross" ],
203213 right_edge = self .__parts ["header_right_tee" ],
204214 filler = self .__parts ["header_row_sep" ],
@@ -208,7 +218,7 @@ def __footer_sep_to_ascii(self) -> str:
208218 """Assembles the seperator below the header of the ascii table"""
209219 return self .__row_to_ascii (
210220 left_edge = self .__parts ["footer_left_tee" ],
211- first_col_sep = self .__parts ["first_col_footer_cross " ],
221+ heading_col_sep = self .__parts ["heading_col_footer_cross " ],
212222 column_seperator = self .__parts ["footer_row_cross" ],
213223 right_edge = self .__parts ["footer_right_tee" ],
214224 filler = self .__parts ["footer_row_sep" ],
@@ -219,7 +229,7 @@ def __body_to_ascii(self) -> str:
219229 for row in self .__body :
220230 output += self .__row_to_ascii (
221231 left_edge = self .__parts ["left_and_right_edge" ],
222- first_col_sep = self .__parts ["first_col_sep " ],
232+ heading_col_sep = self .__parts ["heading_col_sep " ],
223233 column_seperator = self .__parts ["middle_edge" ],
224234 right_edge = self .__parts ["left_and_right_edge" ],
225235 filler = row ,
@@ -250,6 +260,7 @@ def table2ascii(
250260 header : Optional [List ] = None ,
251261 body : Optional [List [List ]] = None ,
252262 footer : Optional [List ] = None ,
263+ ** options ,
253264) -> str :
254265 """Convert a 2D Python table to ASCII text
255266
@@ -258,4 +269,4 @@ def table2ascii(
258269 :param body: :class:`Optional[List[List]]` 2-dimensional list of values in the table's body
259270 :param footer: :class:`Optional[List]` List of column values in the table's footer row
260271 """
261- return TableToAscii (header , body , footer ).to_ascii ()
272+ return TableToAscii (header , body , footer , Options ( ** options ) ).to_ascii ()
0 commit comments