Skip to content

Commit ea432b5

Browse files
feat!: Added the ability to merge cells with Merge.LEFT (#67)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9ad460f commit ea432b5

File tree

12 files changed

+758
-717
lines changed

12 files changed

+758
-717
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,40 @@ print(output)
159159
"""
160160
```
161161

162+
### 🪄 Merge adjacent cells
163+
164+
```py
165+
from table2ascii import table2ascii, Merge, PresetStyle
166+
167+
output = table2ascii(
168+
header=["#", "G", "Merge", Merge.LEFT, "S"],
169+
body=[
170+
[1, 5, 6, 200, Merge.LEFT],
171+
[2, "E", "Long cell", Merge.LEFT, Merge.LEFT],
172+
["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"],
173+
],
174+
footer=["SUM", "100", "200", Merge.LEFT, "300"],
175+
style=PresetStyle.double_thin_box,
176+
first_col_heading=True,
177+
)
178+
179+
print(output)
180+
181+
"""
182+
╔═════╦═════╤═══════╤═════╗
183+
║ # ║ G │ Merge │ S ║
184+
╠═════╬═════╪═══╤═══╧═════╣
185+
║ 1 ║ 5 │ 6 │ 200 ║
186+
╟─────╫─────┼───┴─────────╢
187+
║ 2 ║ E │ Long cell ║
188+
╟─────╨─────┴───┬───┬─────╢
189+
║ Bonus │ F │ G ║
190+
╠═════╦═════╤═══╧═══╪═════╣
191+
║ SUM ║ 100 │ 200 │ 300 ║
192+
╚═════╩═════╧═══════╧═════╝
193+
"""
194+
```
195+
162196
## ⚙️ Options
163197

164198
All parameters are optional.

docs/source/api.rst

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ table2ascii
88

99
.. autofunction:: table2ascii
1010

11-
1211
Alignment
1312
~~~~~~~~~
1413

1514
.. autoenum:: Alignment
1615
:members:
1716

17+
.. _Merge:
18+
19+
Merge
20+
~~~~~
21+
22+
.. autoenum:: Merge
23+
:members:
24+
1825
PresetStyle
1926
~~~~~~~~~~~
2027

@@ -51,3 +58,10 @@ Exceptions
5158
.. autoexception:: table2ascii.exceptions.ColumnWidthTooSmallError
5259

5360
.. autoexception:: table2ascii.exceptions.InvalidAlignmentError
61+
62+
.. autoexception:: table2ascii.exceptions.TableStyleTooLongError
63+
64+
Warnings
65+
~~~~~~~~
66+
67+
.. autoclass:: table2ascii.exceptions.TableStyleTooShortWarning

docs/source/styles.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,29 @@ Preset styles
286286
║ 2 ║ 30 40 35 30 ║
287287
╚═══╩═══════════════════╝
288288
289+
.. _PresetStyle.double_thin_box:
290+
291+
`double_thin_box`
292+
~~~~~~~~~~~~~~~~~
293+
294+
.. code-block:: none
295+
296+
╔═════╦═════╤═════╤═════╤═════╗
297+
║ # ║ G │ H │ R │ S ║
298+
╠═════╬═════╪═════╪═════╪═════╣
299+
║ 1 ║ 30 │ 40 │ 35 │ 30 ║
300+
╟─────╫─────┼─────┼─────┼─────╢
301+
║ 2 ║ 30 │ 40 │ 35 │ 30 ║
302+
╠═════╬═════╪═════╪═════╪═════╣
303+
║ SUM ║ 130 │ 140 │ 135 │ 130 ║
304+
╚═════╩═════╧═════╧═════╧═════╝
305+
306+
╔═══╦════╤════╤════╤════╗
307+
║ 1 ║ 30 │ 40 │ 35 │ 30 ║
308+
╟───╫────┼────┼────┼────╢
309+
║ 2 ║ 30 │ 40 │ 35 │ 30 ║
310+
╚═══╩════╧════╧════╧════╝
311+
289312
.. _PresetStyle.double_thin_compact:
290313

291314
`double_thin_compact`

docs/source/usage.rst

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ Set column widths and alignments
5959
header=["#", "G", "H", "R", "S"],
6060
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
6161
first_col_heading=True,
62-
column_widths=[5] * 5, # [5, 5, 5, 5, 5]
63-
alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4, # First is left, remaining 4 are right
62+
column_widths=[5, 5, 5, 5, 5],
63+
alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4,
6464
)
6565
6666
print(output)
@@ -84,7 +84,7 @@ Use a preset style
8484
output = table2ascii(
8585
header=["First", "Second", "Third", "Fourth"],
8686
body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]],
87-
column_widths=[10] * 4,
87+
column_widths=[10, 10, 10, 10],
8888
style=PresetStyle.ascii_box
8989
)
9090
@@ -130,7 +130,7 @@ Check :ref:`TableStyle` for more info.
130130
output = table2ascii(
131131
header=["First", "Second", "Third"],
132132
body=[["10", "30", "40"], ["20", "10", "20"], ["30", "20", "30"]],
133-
style=my_style
133+
style=my_style,
134134
)
135135
136136
print(output)
@@ -143,4 +143,41 @@ Check :ref:`TableStyle` for more info.
143143
| 20 : 10 : 20 |
144144
| 30 : 20 : 30 |
145145
*-------'--------'-------*
146-
"""
146+
"""
147+
148+
Merge adjacent cells
149+
~~~~~~~~~~~~~~~~~~~~
150+
151+
Check :ref:`Merge` for more info.
152+
153+
.. code:: py
154+
155+
from table2ascii import table2ascii, Merge, PresetStyle
156+
157+
output = table2ascii(
158+
header=["#", "G", "Merge", Merge.LEFT, "S"],
159+
body=[
160+
[1, 5, 6, 200, Merge.LEFT],
161+
[2, "E", "Long cell", Merge.LEFT, Merge.LEFT],
162+
["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"],
163+
],
164+
footer=["SUM", "100", "200", Merge.LEFT, "300"],
165+
style=PresetStyle.double_thin_box,
166+
first_col_heading=True,
167+
)
168+
169+
print(output)
170+
171+
"""
172+
╔═════╦═════╤═══════╤═════╗
173+
║ # ║ G │ Merge │ S ║
174+
╠═════╬═════╪═══╤═══╧═════╣
175+
║ 1 ║ 5 │ 6 │ 200 ║
176+
╟─────╫─────┼───┴─────────╢
177+
║ 2 ║ E │ Long cell ║
178+
╟─────╨─────┴───┬───┬─────╢
179+
║ Bonus │ F │ G ║
180+
╠═════╦═════╤═══╧═══╪═════╣
181+
║ SUM ║ 100 │ 200 │ 300 ║
182+
╚═════╩═════╧═══════╧═════╝
183+
"""

table2ascii/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
"""
44

55
from .alignment import Alignment
6+
from .merge import Merge
67
from .preset_style import PresetStyle
78
from .table_style import TableStyle
89
from .table_to_ascii import table2ascii
910

1011
__version__ = "1.0.0"
1112

1213
__all__ = [
13-
"table2ascii",
1414
"Alignment",
15-
"TableStyle",
15+
"Merge",
1616
"PresetStyle",
17+
"TableStyle",
18+
"table2ascii",
1719
]

table2ascii/exceptions.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,54 @@ def _message(self):
189189
f"Invalid alignment: {self.alignment!r} is not a valid alignment. "
190190
f"Valid alignments are: {', '.join(a.__repr__() for a in Alignment)}"
191191
)
192+
193+
194+
class TableStyleTooLongError(Table2AsciiError, ValueError):
195+
"""Exception raised when the number of characters passed in the string
196+
for creating the table style exceeds the number of parameters that the
197+
table style accepts
198+
199+
This class is a subclass of :class:`Table2AsciiError` and :class:`ValueError`.
200+
201+
Attributes:
202+
string (str): The string that caused the error
203+
max_characters (int): The maximum number of characters that are allowed
204+
"""
205+
206+
def __init__(self, string: str, max_characters: int):
207+
self.string = string
208+
self.max_characters = max_characters
209+
super().__init__(self._message())
210+
211+
def _message(self):
212+
return (
213+
f"Too many characters for table style: {len(self.string)} characters "
214+
f"found, but the maximum number of characters allowed is {self.max_characters}."
215+
)
216+
217+
218+
class TableStyleTooShortWarning(UserWarning):
219+
"""Warning raised when the number of characters passed in the string
220+
for creating the table style is fewer than the number of parameters
221+
that the table style accepts
222+
223+
This class is a subclass of :class:`UserWarning`.
224+
225+
It can be silenced using :func:`warnings.filterwarnings`.
226+
227+
Attributes:
228+
string (str): The string that caused the warning
229+
max_characters (int): The number of characters that :class:`TableStyle` accepts
230+
"""
231+
232+
def __init__(self, string: str, max_characters: int):
233+
self.string = string
234+
self.max_characters = max_characters
235+
super().__init__(self._message())
236+
237+
def _message(self):
238+
return (
239+
f"Too few characters for table style: {len(self.string)} characters "
240+
f"found, but table styles can accept {self.max_characters} characters. "
241+
f"Missing characters will be replaced with spaces."
242+
)

table2ascii/merge.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from enum import Enum
2+
3+
4+
class Merge(Enum):
5+
"""Enum for merging table cells
6+
7+
Using :attr:`Merge.LEFT` in a table cell will merge the cell it is used in
8+
with the cell to its left.
9+
10+
In the case that the contents of the merged cell are longer than the
11+
combined widths of the unmerged cells in the rows above and below,
12+
the merged cell will be wrapped onto multiple lines. The ``column_widths``
13+
option can be used to control the widths of the unmerged cells.
14+
15+
Example::
16+
17+
from table2ascii import table2ascii, Merge, PresetStyle
18+
19+
table2ascii(
20+
header=["Name", "Price", "Category", "Stock"],
21+
body=[["Milk", "$2.99", "N/A", Merge.LEFT]],
22+
footer=["Description", "Milk is a nutritious beverage", Merge.LEFT, Merge.LEFT],
23+
style=PresetStyle.double_box,
24+
)
25+
26+
\"\"\"
27+
╔═════════════╦═══════╦══════════╦═══════╗
28+
║ Name ║ Price ║ Category ║ Stock ║
29+
╠═════════════╬═══════╬══════════╩═══════╣
30+
║ Milk ║ $2.99 ║ N/A ║
31+
╠═════════════╬═══════╩══════════════════╣
32+
║ Description ║ Milk is a nutritious ║
33+
║ ║ beverage ║
34+
╚═════════════╩══════════════════════════╝
35+
\"\"\"
36+
37+
.. versionadded:: 1.0.0
38+
"""
39+
40+
LEFT = 0
41+
42+
def __str__(self):
43+
return ""

table2ascii/preset_style.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,34 @@ class PresetStyle:
1616
)
1717
"""
1818

19-
thin = TableStyle.from_string("┌─┬─┐││ ├─┼─┤├─┼─┤└┴─┘")
20-
thin_box = TableStyle.from_string("┌─┬┬┐│││├─┼┼┤├─┼┼┤└┴┴┘")
21-
thin_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤├─┼─┤╰┴─╯")
22-
thin_compact = TableStyle.from_string("┌─┬─┐││ ├─┼─┤ └┴─┘")
23-
thin_compact_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤ ╰┴─╯")
24-
thin_thick = TableStyle.from_string("┌─┬─┐││ ┝━┿━┥├─┼─┤└┴─┘")
25-
thin_thick_rounded = TableStyle.from_string("╭─┬─╮││ ┝━┿━┥├─┼─┤╰┴─╯")
26-
thin_double = TableStyle.from_string("┌─┬─┐││ ╞═╪═╡├─┼─┤└┴─┘")
27-
thin_double_rounded = TableStyle.from_string("╭─┬─╮││ ╞═╪═╡├─┼─┤╰┴─╯")
28-
thick = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫┣━╋━┫┗┻━┛")
29-
thick_box = TableStyle.from_string("┏━┳┳┓┃┃┃┣━╋╋┫┣━╋╋┫┗┻┻┛")
30-
thick_compact = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫ ┗┻━┛")
31-
double = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣╠═╬═╣╚╩═╝")
32-
double_box = TableStyle.from_string("╔═╦╦╗║║║╠═╬╬╣╠═╬╬╣╚╩╩╝")
33-
double_compact = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣ ╚╩═╝")
34-
double_thin_compact = TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝")
35-
minimalist = TableStyle.from_string(" ─── │ ━━━ ─── ── ")
36-
borderless = TableStyle.from_string(" ┃ ━ ")
37-
simple = TableStyle.from_string(" ═ ║ ═ ")
38-
ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+")
39-
ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++")
40-
ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+")
41-
ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+")
42-
ascii_minimalist = TableStyle.from_string(" --- | === --- -- ")
43-
ascii_borderless = TableStyle.from_string(" | - ")
44-
ascii_simple = TableStyle.from_string(" = | = ")
45-
ascii_rounded = TableStyle.from_string(r"/===\|| |=|=||-|-|\|=/")
46-
ascii_rounded_box = TableStyle.from_string(r"/===\||||=||||-|||\||/")
47-
markdown = TableStyle.from_string(" ||||-||| ")
48-
plain = TableStyle.from_string(" ").set(left_and_right_edge="")
19+
thin = TableStyle.from_string("┌─┬─┐││ ├─┼─┤├─┼─┤└┴─┘────┬┴┬┴")
20+
thin_box = TableStyle.from_string("┌─┬┬┐│││├─┼┼┤├─┼┼┤└┴┴┘┬┴┬┴┬┴┬┴")
21+
thin_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤├─┼─┤╰┴─╯────┬┴┬┴")
22+
thin_compact = TableStyle.from_string("┌─┬─┐││ ├─┼─┤ └┴─┘ ── ┬┴")
23+
thin_compact_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤ ╰┴─╯ ── ┬┴")
24+
thin_thick = TableStyle.from_string("┌─┬─┐││ ┝━┿━┥├─┼─┤└┴─┘──━━┬┴┯┷")
25+
thin_thick_rounded = TableStyle.from_string("╭─┬─╮││ ┝━┿━┥├─┼─┤╰┴─╯──━━┬┴┯┷")
26+
thin_double = TableStyle.from_string("┌─┬─┐││ ╞═╪═╡├─┼─┤└┴─┘──══┬┴╤╧")
27+
thin_double_rounded = TableStyle.from_string("╭─┬─╮││ ╞═╪═╡├─┼─┤╰┴─╯──══┬┴╤╧")
28+
thick = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫┣━╋━┫┗┻━┛━━━━┳┻┳┻")
29+
thick_box = TableStyle.from_string("┏━┳┳┓┃┃┃┣━╋╋┫┣━╋╋┫┗┻┻┛┳┻┳┻┳┻┳┻")
30+
thick_compact = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫ ┗┻━┛ ━━ ┳┻")
31+
double = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣╠═╬═╣╚╩═╝════╦╩╦╩")
32+
double_box = TableStyle.from_string("╔═╦╦╗║║║╠═╬╬╣╠═╬╬╣╚╩╩╝╦╩╦╩╦╩╦╩")
33+
double_compact = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣ ╚╩═╝ ══ ╦╩")
34+
double_thin_box = TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧╥╨╦╩")
35+
double_thin_compact = TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝ ── ╥╨")
36+
minimalist = TableStyle.from_string(" ─── │ ━━━ ─── ── ──━━┬┴┯┷")
37+
borderless = TableStyle.from_string(" ┃ ━ ━━ ━━")
38+
simple = TableStyle.from_string(" ═ ║ ═ ══ ╦╩")
39+
ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+----++++")
40+
ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++++++++++")
41+
ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+ -- --")
42+
ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+--==--==")
43+
ascii_minimalist = TableStyle.from_string(" --- | === --- -- --==--==")
44+
ascii_borderless = TableStyle.from_string(" | - -- --")
45+
ascii_simple = TableStyle.from_string(" = | = == ==")
46+
ascii_rounded = TableStyle.from_string(r"/===\|| |=|=||-|-|\|=/--==--==")
47+
ascii_rounded_box = TableStyle.from_string(r"/===\||||=||||-|||\||/--==--==")
48+
markdown = TableStyle.from_string(" ||||-||| -- --")
49+
plain = TableStyle.from_string(" " * 30).set(left_and_right_edge="")

0 commit comments

Comments
 (0)