1
- from typing import List , Optional , Union
2
- from math import floor , ceil
3
1
import 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"""
4
10
11
+ first_col_heading : bool = False
12
+ last_col_heading : bool = False
13
+
14
+
15
+ class Alignment (enum .Enum ):
16
+ """Enum for alignment types"""
5
17
6
- class Align (enum .Enum ):
7
18
LEFT = 0
8
19
RIGHT = 1
9
20
CENTER = 2
@@ -17,6 +28,7 @@ def __init__(
17
28
header : Optional [List ],
18
29
body : Optional [List [List ]],
19
30
footer : Optional [List ],
31
+ options : Options ,
20
32
):
21
33
"""Validate arguments and initialize fields"""
22
34
# check if columns in header are different from footer
@@ -36,6 +48,7 @@ def __init__(
36
48
self .__header = header
37
49
self .__body = body
38
50
self .__footer = footer
51
+ self .__options = options
39
52
self .__columns = self .__count_columns ()
40
53
self .__cell_widths = self .__get_column_widths ()
41
54
@@ -52,24 +65,24 @@ def __init__(
52
65
self .__parts = {
53
66
"top_left_corner" : "╔" , # A
54
67
"top_and_bottom_edge" : "═" , # B
55
- "first_col_top_tee " : "╦" , # C
68
+ "heading_col_top_tee " : "╦" , # C
56
69
"top_tee" : "═" , # D
57
70
"top_right_corner" : "╗" , # E
58
71
"left_and_right_edge" : "║" , # F
59
- "first_col_sep " : "║" , # G
72
+ "heading_col_sep " : "║" , # G
60
73
"middle_edge" : " " , # H
61
74
"header_left_tee" : "╟" , # I
62
75
"header_row_sep" : "─" , # J
63
- "first_col_header_cross " : "╫" , # K
76
+ "heading_col_header_cross " : "╫" , # K
64
77
"header_row_cross" : "─" , # L
65
78
"header_right_tee" : "╢" , # M
66
79
"footer_left_tee" : "╟" , # N
67
80
"footer_row_sep" : "─" , # O
68
- "first_col_footer_cross " : "╫" , # P
81
+ "heading_col_footer_cross " : "╫" , # P
69
82
"footer_row_cross" : "─" , # Q
70
83
"footer_right_tee" : "╢" , # R
71
84
"bottom_left_corner" : "╚" , # S
72
- "first_col_bottom_tee " : "╩" , # T
85
+ "heading_col_bottom_tee " : "╩" , # T
73
86
"bottom_tee" : "═" , # U
74
87
"bottom_right_corner" : "╝" , # V
75
88
}
@@ -102,44 +115,36 @@ def __get_column_widths(self) -> List[int]:
102
115
col_counts .append (max (header_size , * body_size , footer_size ) + 2 )
103
116
return col_counts
104
117
105
- def __pad (self , text : str , width : int , alignment : Align = Align .CENTER ):
118
+ def __pad (self , text : str , width : int , alignment : Alignment = Alignment .CENTER ):
106
119
"""Pad a string of text to a given width with specified alignment"""
107
- if alignment == Align .LEFT :
120
+ if alignment == Alignment .LEFT :
108
121
# pad with spaces on the end
109
122
return f" { text } " + (" " * (width - len (text ) - 2 ))
110
- if alignment == Align .CENTER :
123
+ if alignment == Alignment .CENTER :
111
124
# pad with spaces, half on each side
112
125
before = " " * floor ((width - len (text ) - 2 ) / 2 )
113
126
after = " " * ceil ((width - len (text ) - 2 ) / 2 )
114
127
return before + f" { text } " + after
115
- if alignment == Align .RIGHT :
128
+ if alignment == Alignment .RIGHT :
116
129
# pad with spaces at the beginning
117
130
return (" " * (width - len (text ) - 2 )) + f" { text } "
118
131
raise ValueError (f"The value '{ alignment } ' is not valid for alignment." )
119
132
120
133
def __row_to_ascii (
121
134
self ,
122
135
left_edge : str ,
123
- first_col_sep : str ,
136
+ heading_col_sep : str ,
124
137
column_seperator : str ,
125
138
right_edge : str ,
126
139
filler : Union [str , List ],
127
140
) -> str :
128
141
"""Assembles a row of the ascii table"""
142
+ first_heading = self .__options .first_col_heading
143
+ last_heading = self .__options .last_col_heading
129
144
# left edge of the row
130
145
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 ):
143
148
# content between separators
144
149
output += (
145
150
# edge or row separator if filler is a specific character
@@ -148,17 +153,22 @@ def __row_to_ascii(
148
153
# otherwise, use the column content
149
154
else self .__pad (str (filler [i ]), self .__cell_widths [i ])
150
155
)
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
155
165
return output + "\n "
156
166
157
167
def __top_edge_to_ascii (self ) -> str :
158
168
"""Assembles the top edge of the ascii table"""
159
169
return self .__row_to_ascii (
160
170
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 " ],
162
172
column_seperator = self .__parts ["top_tee" ],
163
173
right_edge = self .__parts ["top_right_corner" ],
164
174
filler = self .__parts ["top_and_bottom_edge" ],
@@ -168,7 +178,7 @@ def __bottom_edge_to_ascii(self) -> str:
168
178
"""Assembles the top edge of the ascii table"""
169
179
return self .__row_to_ascii (
170
180
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 " ],
172
182
column_seperator = self .__parts ["bottom_tee" ],
173
183
right_edge = self .__parts ["bottom_right_corner" ],
174
184
filler = self .__parts ["top_and_bottom_edge" ],
@@ -178,7 +188,7 @@ def __header_row_to_ascii(self) -> str:
178
188
"""Assembles the header row line of the ascii table"""
179
189
return self .__row_to_ascii (
180
190
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 " ],
182
192
column_seperator = self .__parts ["middle_edge" ],
183
193
right_edge = self .__parts ["left_and_right_edge" ],
184
194
filler = self .__header ,
@@ -188,7 +198,7 @@ def __footer_row_to_ascii(self) -> str:
188
198
"""Assembles the header row line of the ascii table"""
189
199
return self .__row_to_ascii (
190
200
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 " ],
192
202
column_seperator = self .__parts ["middle_edge" ],
193
203
right_edge = self .__parts ["left_and_right_edge" ],
194
204
filler = self .__footer ,
@@ -198,7 +208,7 @@ def __header_sep_to_ascii(self) -> str:
198
208
"""Assembles the seperator below the header of the ascii table"""
199
209
return self .__row_to_ascii (
200
210
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 " ],
202
212
column_seperator = self .__parts ["header_row_cross" ],
203
213
right_edge = self .__parts ["header_right_tee" ],
204
214
filler = self .__parts ["header_row_sep" ],
@@ -208,7 +218,7 @@ def __footer_sep_to_ascii(self) -> str:
208
218
"""Assembles the seperator below the header of the ascii table"""
209
219
return self .__row_to_ascii (
210
220
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 " ],
212
222
column_seperator = self .__parts ["footer_row_cross" ],
213
223
right_edge = self .__parts ["footer_right_tee" ],
214
224
filler = self .__parts ["footer_row_sep" ],
@@ -219,7 +229,7 @@ def __body_to_ascii(self) -> str:
219
229
for row in self .__body :
220
230
output += self .__row_to_ascii (
221
231
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 " ],
223
233
column_seperator = self .__parts ["middle_edge" ],
224
234
right_edge = self .__parts ["left_and_right_edge" ],
225
235
filler = row ,
@@ -250,6 +260,7 @@ def table2ascii(
250
260
header : Optional [List ] = None ,
251
261
body : Optional [List [List ]] = None ,
252
262
footer : Optional [List ] = None ,
263
+ ** options ,
253
264
) -> str :
254
265
"""Convert a 2D Python table to ASCII text
255
266
@@ -258,4 +269,4 @@ def table2ascii(
258
269
:param body: :class:`Optional[List[List]]` 2-dimensional list of values in the table's body
259
270
:param footer: :class:`Optional[List]` List of column values in the table's footer row
260
271
"""
261
- return TableToAscii (header , body , footer ).to_ascii ()
272
+ return TableToAscii (header , body , footer , Options ( ** options ) ).to_ascii ()
0 commit comments