Skip to content

Commit 8f5b1de

Browse files
authored
Merge pull request #5 from DenverCoder1/col-heading-option
2 parents d0f7e02 + 0d18e28 commit 8f5b1de

File tree

5 files changed

+166
-213
lines changed

5 files changed

+166
-213
lines changed

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,23 @@ output = table2ascii(
3636
print(output)
3737

3838
"""
39-
╔════════════════════════════╗
40-
║ # G H R S ║
41-
╟────────────────────────────╢
42-
║ 1 30 40 35 30 ║
43-
║ 2 30 40 35 30 ║
44-
╟────────────────────────────╢
45-
║ SUM 130 140 135 130 ║
46-
╚════════════════════════════╝
39+
╔════════════════════════════╗
40+
║ # G H R S ║
41+
╟────────────────────────────╢
42+
║ 1 30 40 35 30
43+
║ 2 30 40 35 30
44+
╟────────────────────────────╢
45+
║ SUM 130 140 135 130 ║
46+
╚════════════════════════════╝
4747
"""
4848
```
4949

5050
```py
5151
from table2ascii import table2ascii
5252

5353
output = table2ascii(
54-
body=[["Assignment", "30", "40", "35", "30"], ["Bonus", "10", "20", "5", "10"]]
54+
body=[["Assignment", "30", "40", "35", "30"], ["Bonus", "10", "20", "5", "10"]],
55+
first_col_heading=True,
5556
)
5657

5758
print(output)
@@ -68,6 +69,11 @@ print(output)
6869

6970
Soon table2ascii will support more options for customization.
7071

72+
| Option | Type | Default | Description |
73+
| :-----------------: | :----: | :-----: | :--------------------------------------------------------------: |
74+
| `first_col_heading` | `bool` | `False` | Whether to add a heading column seperator after the first column |
75+
| `last_col_heading` | `bool` | `False` | Whether to add a heading column seperator before the last column |
76+
7177
## 👨‍🎨 Use cases
7278

7379
### Discord messages and embeds

table2ascii/__init__.py

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1-
from typing import List, Optional, Union
2-
from math import floor, ceil
31
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"""
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()

tests/test_convert.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def test_header_body_footer():
88
header=["#", "G", "H", "R", "S"],
99
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
1010
footer=["SUM", "130", "140", "135", "130"],
11+
first_col_heading=True,
1112
)
1213
expected = (
1314
"╔═════╦═══════════════════════╗\n"
@@ -26,6 +27,7 @@ def test_body_footer():
2627
text = t2a(
2728
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
2829
footer=["SUM", "130", "140", "135", "130"],
30+
first_col_heading=True,
2931
)
3032
expected = (
3133
"╔═════╦═══════════════════════╗\n"
@@ -42,6 +44,7 @@ def test_header_body():
4244
text = t2a(
4345
header=["#", "G", "H", "R", "S"],
4446
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
47+
first_col_heading=True,
4548
)
4649
expected = (
4750
"╔═══╦═══════════════════╗\n"
@@ -58,6 +61,7 @@ def test_header_footer():
5861
text = t2a(
5962
header=["#", "G", "H", "R", "S"],
6063
footer=["SUM", "130", "140", "135", "130"],
64+
first_col_heading=True,
6165
)
6266
expected = (
6367
"╔═════╦═══════════════════════╗\n"
@@ -73,6 +77,7 @@ def test_header_footer():
7377
def test_header():
7478
text = t2a(
7579
header=["#", "G", "H", "R", "S"],
80+
first_col_heading=True,
7681
)
7782
expected = (
7883
"╔═══╦═══════════════╗\n"
@@ -86,6 +91,7 @@ def test_header():
8691
def test_body():
8792
text = t2a(
8893
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
94+
first_col_heading=True,
8995
)
9096
expected = (
9197
"╔═══╦═══════════════════╗\n"
@@ -99,6 +105,7 @@ def test_body():
99105
def test_footer():
100106
text = t2a(
101107
footer=["SUM", "130", "140", "135", "130"],
108+
first_col_heading=True,
102109
)
103110
expected = (
104111
"╔═════╦═══════════════════════╗\n"
@@ -114,6 +121,7 @@ def test_header_footer_unequal():
114121
t2a(
115122
header=["H", "R", "S"],
116123
footer=["SUM", "130", "140", "135", "130"],
124+
first_col_heading=True,
117125
)
118126

119127

@@ -126,6 +134,7 @@ def test_header_body_unequal():
126134
["1", "30", "40", "35", "30", "36"],
127135
["2", "30", "40", "35", "30"],
128136
],
137+
first_col_heading=True,
129138
)
130139

131140

@@ -138,13 +147,15 @@ def test_footer_body_unequal():
138147
["2", "30", "40", "35", "30"],
139148
],
140149
footer=["SUM", "130", "140", "135", "130", "36"],
150+
first_col_heading=True,
141151
)
142152

143153

144154
def test_empty_header():
145155
text = t2a(
146156
header=[],
147157
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
158+
first_col_heading=True,
148159
)
149160
expected = (
150161
"╔═══╦═══════════════════╗\n"
@@ -156,7 +167,11 @@ def test_empty_header():
156167

157168

158169
def test_empty_body():
159-
text = t2a(header=["#", "G", "H", "R", "S"], body=[])
170+
text = t2a(
171+
header=["#", "G", "H", "R", "S"],
172+
body=[],
173+
first_col_heading=True,
174+
)
160175
expected = (
161176
"╔═══╦═══════════════╗\n"
162177
"║ # ║ G H R S ║\n"

0 commit comments

Comments
 (0)