Skip to content

feat!: Added the ability to merge cells with Merge.LEFT #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9964852
feat: Initial Merge.LEFT implementation
DenverCoder1 Dec 12, 2022
b91e5a0
Wrap long lines in merged cells
DenverCoder1 Dec 12, 2022
d36a2d8
Fix wrap edge case
DenverCoder1 Dec 12, 2022
f7f5db9
feat!: Merge cell for table styles
DenverCoder1 Dec 12, 2022
e427da2
Update table_style.py
DenverCoder1 Dec 12, 2022
e4b7aa0
Update merge.py
DenverCoder1 Dec 12, 2022
b32822c
Update __init__.py
DenverCoder1 Dec 12, 2022
0fe2f71
Update documentation
DenverCoder1 Dec 12, 2022
f7c6ca4
feat: double thin box style
DenverCoder1 Dec 12, 2022
d200275
Add parts for supporting header column merging
DenverCoder1 Dec 12, 2022
0687a91
Update test_merge.py
DenverCoder1 Dec 12, 2022
8c3a3c5
Support for merging header columns
DenverCoder1 Dec 12, 2022
3c965d4
refactor
DenverCoder1 Dec 12, 2022
5efcb0d
style: auto fixes from pre-commit hooks
pre-commit-ci[bot] Dec 12, 2022
3935a8f
top and bottom edge tee fixes
DenverCoder1 Dec 12, 2022
9d96c70
Update preset_style.py
DenverCoder1 Dec 12, 2022
b7c5c61
Add compact test
DenverCoder1 Dec 12, 2022
c59ee9f
Update documentation
DenverCoder1 Dec 12, 2022
4d5e4ee
Remove unused references
DenverCoder1 Dec 12, 2022
5259a84
Update docstring
DenverCoder1 Dec 12, 2022
12cd4a5
Custom tablestyle warnings and exceptions
DenverCoder1 Dec 12, 2022
1dae783
Clean up and add to styles tests
DenverCoder1 Dec 12, 2022
4765a3b
Change min to max_characters
DenverCoder1 Dec 12, 2022
1c77e2b
fix rows beginning with Merge.LEFT
DenverCoder1 Dec 12, 2022
f5d00bc
Update docstring
DenverCoder1 Dec 12, 2022
a0c3b3e
Fix docs link
DenverCoder1 Dec 12, 2022
5080127
refactor: readability of plain
DenverCoder1 Dec 12, 2022
7dfbd5c
Merge branch 'main' into merge
DenverCoder1 Dec 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,40 @@ print(output)
"""
```

### 🪄 Merge adjacent cells

```py
from table2ascii import table2ascii, Merge, PresetStyle

output = table2ascii(
header=["#", "G", "Merge", Merge.LEFT, "S"],
body=[
[1, 5, 6, 200, Merge.LEFT],
[2, "E", "Long cell", Merge.LEFT, Merge.LEFT],
["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"],
],
footer=["SUM", "100", "200", Merge.LEFT, "300"],
style=PresetStyle.double_thin_box,
first_col_heading=True,
)

print(output)

"""
╔═════╦═════╤═══════╤═════╗
║ # ║ G │ Merge │ S ║
╠═════╬═════╪═══╤═══╧═════╣
║ 1 ║ 5 │ 6 │ 200 ║
╟─────╫─────┼───┴─────────╢
║ 2 ║ E │ Long cell ║
╟─────╨─────┴───┬───┬─────╢
║ Bonus │ F │ G ║
╠═════╦═════╤═══╧═══╪═════╣
║ SUM ║ 100 │ 200 │ 300 ║
╚═════╩═════╧═══════╧═════╝
"""
```

## ⚙️ Options

All parameters are optional.
Expand Down
16 changes: 15 additions & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ table2ascii

.. autofunction:: table2ascii


Alignment
~~~~~~~~~

.. autoenum:: Alignment
:members:

.. _Merge:

Merge
~~~~~

.. autoenum:: Merge
:members:

PresetStyle
~~~~~~~~~~~

Expand Down Expand Up @@ -51,3 +58,10 @@ Exceptions
.. autoexception:: table2ascii.exceptions.ColumnWidthTooSmallError

.. autoexception:: table2ascii.exceptions.InvalidAlignmentError

.. autoexception:: table2ascii.exceptions.TableStyleTooLongError

Warnings
~~~~~~~~

.. autoclass:: table2ascii.exceptions.TableStyleTooShortWarning
23 changes: 23 additions & 0 deletions docs/source/styles.rst
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,29 @@ Preset styles
║ 2 ║ 30 40 35 30 ║
╚═══╩═══════════════════╝

.. _PresetStyle.double_thin_box:

`double_thin_box`
~~~~~~~~~~~~~~~~~

.. code-block:: none

╔═════╦═════╤═════╤═════╤═════╗
║ # ║ G │ H │ R │ S ║
╠═════╬═════╪═════╪═════╪═════╣
║ 1 ║ 30 │ 40 │ 35 │ 30 ║
╟─────╫─────┼─────┼─────┼─────╢
║ 2 ║ 30 │ 40 │ 35 │ 30 ║
╠═════╬═════╪═════╪═════╪═════╣
║ SUM ║ 130 │ 140 │ 135 │ 130 ║
╚═════╩═════╧═════╧═════╧═════╝

╔═══╦════╤════╤════╤════╗
║ 1 ║ 30 │ 40 │ 35 │ 30 ║
╟───╫────┼────┼────┼────╢
║ 2 ║ 30 │ 40 │ 35 │ 30 ║
╚═══╩════╧════╧════╧════╝

.. _PresetStyle.double_thin_compact:

`double_thin_compact`
Expand Down
47 changes: 42 additions & 5 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ Set column widths and alignments
header=["#", "G", "H", "R", "S"],
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
first_col_heading=True,
column_widths=[5] * 5, # [5, 5, 5, 5, 5]
alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4, # First is left, remaining 4 are right
column_widths=[5, 5, 5, 5, 5],
alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4,
)

print(output)
Expand All @@ -84,7 +84,7 @@ Use a preset style
output = table2ascii(
header=["First", "Second", "Third", "Fourth"],
body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]],
column_widths=[10] * 4,
column_widths=[10, 10, 10, 10],
style=PresetStyle.ascii_box
)

Expand Down Expand Up @@ -130,7 +130,7 @@ Check :ref:`TableStyle` for more info.
output = table2ascii(
header=["First", "Second", "Third"],
body=[["10", "30", "40"], ["20", "10", "20"], ["30", "20", "30"]],
style=my_style
style=my_style,
)

print(output)
Expand All @@ -143,4 +143,41 @@ Check :ref:`TableStyle` for more info.
| 20 : 10 : 20 |
| 30 : 20 : 30 |
*-------'--------'-------*
"""
"""

Merge adjacent cells
~~~~~~~~~~~~~~~~~~~~

Check :ref:`Merge` for more info.

.. code:: py

from table2ascii import table2ascii, Merge, PresetStyle

output = table2ascii(
header=["#", "G", "Merge", Merge.LEFT, "S"],
body=[
[1, 5, 6, 200, Merge.LEFT],
[2, "E", "Long cell", Merge.LEFT, Merge.LEFT],
["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"],
],
footer=["SUM", "100", "200", Merge.LEFT, "300"],
style=PresetStyle.double_thin_box,
first_col_heading=True,
)

print(output)

"""
╔═════╦═════╤═══════╤═════╗
║ # ║ G │ Merge │ S ║
╠═════╬═════╪═══╤═══╧═════╣
║ 1 ║ 5 │ 6 │ 200 ║
╟─────╫─────┼───┴─────────╢
║ 2 ║ E │ Long cell ║
╟─────╨─────┴───┬───┬─────╢
║ Bonus │ F │ G ║
╠═════╦═════╤═══╧═══╪═════╣
║ SUM ║ 100 │ 200 │ 300 ║
╚═════╩═════╧═══════╧═════╝
"""
6 changes: 4 additions & 2 deletions table2ascii/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
"""

from .alignment import Alignment
from .merge import Merge
from .preset_style import PresetStyle
from .table_style import TableStyle
from .table_to_ascii import table2ascii

__version__ = "1.0.0"

__all__ = [
"table2ascii",
"Alignment",
"TableStyle",
"Merge",
"PresetStyle",
"TableStyle",
"table2ascii",
]
51 changes: 51 additions & 0 deletions table2ascii/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,54 @@ def _message(self):
f"Invalid alignment: {self.alignment!r} is not a valid alignment. "
f"Valid alignments are: {', '.join(a.__repr__() for a in Alignment)}"
)


class TableStyleTooLongError(Table2AsciiError, ValueError):
"""Exception raised when the number of characters passed in the string
for creating the table style exceeds the number of parameters that the
table style accepts

This class is a subclass of :class:`Table2AsciiError` and :class:`ValueError`.

Attributes:
string (str): The string that caused the error
max_characters (int): The maximum number of characters that are allowed
"""

def __init__(self, string: str, max_characters: int):
self.string = string
self.max_characters = max_characters
super().__init__(self._message())

def _message(self):
return (
f"Too many characters for table style: {len(self.string)} characters "
f"found, but the maximum number of characters allowed is {self.max_characters}."
)


class TableStyleTooShortWarning(UserWarning):
"""Warning raised when the number of characters passed in the string
for creating the table style is fewer than the number of parameters
that the table style accepts

This class is a subclass of :class:`UserWarning`.

It can be silenced using :func:`warnings.filterwarnings`.

Attributes:
string (str): The string that caused the warning
max_characters (int): The number of characters that :class:`TableStyle` accepts
"""

def __init__(self, string: str, max_characters: int):
self.string = string
self.max_characters = max_characters
super().__init__(self._message())

def _message(self):
return (
f"Too few characters for table style: {len(self.string)} characters "
f"found, but table styles can accept {self.max_characters} characters. "
f"Missing characters will be replaced with spaces."
)
43 changes: 43 additions & 0 deletions table2ascii/merge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from enum import Enum


class Merge(Enum):
"""Enum for merging table cells

Using :attr:`Merge.LEFT` in a table cell will merge the cell it is used in
with the cell to its left.

In the case that the contents of the merged cell are longer than the
combined widths of the unmerged cells in the rows above and below,
the merged cell will be wrapped onto multiple lines. The ``column_widths``
option can be used to control the widths of the unmerged cells.

Example::

from table2ascii import table2ascii, Merge, PresetStyle

table2ascii(
header=["Name", "Price", "Category", "Stock"],
body=[["Milk", "$2.99", "N/A", Merge.LEFT]],
footer=["Description", "Milk is a nutritious beverage", Merge.LEFT, Merge.LEFT],
style=PresetStyle.double_box,
)

\"\"\"
╔═════════════╦═══════╦══════════╦═══════╗
║ Name ║ Price ║ Category ║ Stock ║
╠═════════════╬═══════╬══════════╩═══════╣
║ Milk ║ $2.99 ║ N/A ║
╠═════════════╬═══════╩══════════════════╣
║ Description ║ Milk is a nutritious ║
║ ║ beverage ║
╚═════════════╩══════════════════════════╝
\"\"\"

.. versionadded:: 1.0.0
"""

LEFT = 0

def __str__(self):
return ""
61 changes: 31 additions & 30 deletions table2ascii/preset_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,34 @@ class PresetStyle:
)
"""

thin = TableStyle.from_string("┌─┬─┐││ ├─┼─┤├─┼─┤└┴─┘")
thin_box = TableStyle.from_string("┌─┬┬┐│││├─┼┼┤├─┼┼┤└┴┴┘")
thin_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤├─┼─┤╰┴─╯")
thin_compact = TableStyle.from_string("┌─┬─┐││ ├─┼─┤ └┴─┘")
thin_compact_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤ ╰┴─╯")
thin_thick = TableStyle.from_string("┌─┬─┐││ ┝━┿━┥├─┼─┤└┴─┘")
thin_thick_rounded = TableStyle.from_string("╭─┬─╮││ ┝━┿━┥├─┼─┤╰┴─╯")
thin_double = TableStyle.from_string("┌─┬─┐││ ╞═╪═╡├─┼─┤└┴─┘")
thin_double_rounded = TableStyle.from_string("╭─┬─╮││ ╞═╪═╡├─┼─┤╰┴─╯")
thick = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫┣━╋━┫┗┻━┛")
thick_box = TableStyle.from_string("┏━┳┳┓┃┃┃┣━╋╋┫┣━╋╋┫┗┻┻┛")
thick_compact = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫ ┗┻━┛")
double = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣╠═╬═╣╚╩═╝")
double_box = TableStyle.from_string("╔═╦╦╗║║║╠═╬╬╣╠═╬╬╣╚╩╩╝")
double_compact = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣ ╚╩═╝")
double_thin_compact = TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝")
minimalist = TableStyle.from_string(" ─── │ ━━━ ─── ── ")
borderless = TableStyle.from_string(" ┃ ━ ")
simple = TableStyle.from_string(" ═ ║ ═ ")
ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+")
ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++")
ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+")
ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+")
ascii_minimalist = TableStyle.from_string(" --- | === --- -- ")
ascii_borderless = TableStyle.from_string(" | - ")
ascii_simple = TableStyle.from_string(" = | = ")
ascii_rounded = TableStyle.from_string(r"/===\|| |=|=||-|-|\|=/")
ascii_rounded_box = TableStyle.from_string(r"/===\||||=||||-|||\||/")
markdown = TableStyle.from_string(" ||||-||| ")
plain = TableStyle.from_string(" ").set(left_and_right_edge="")
thin = TableStyle.from_string("┌─┬─┐││ ├─┼─┤├─┼─┤└┴─┘────┬┴┬┴")
thin_box = TableStyle.from_string("┌─┬┬┐│││├─┼┼┤├─┼┼┤└┴┴┘┬┴┬┴┬┴┬┴")
thin_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤├─┼─┤╰┴─╯────┬┴┬┴")
thin_compact = TableStyle.from_string("┌─┬─┐││ ├─┼─┤ └┴─┘ ── ┬┴")
thin_compact_rounded = TableStyle.from_string("╭─┬─╮││ ├─┼─┤ ╰┴─╯ ── ┬┴")
thin_thick = TableStyle.from_string("┌─┬─┐││ ┝━┿━┥├─┼─┤└┴─┘──━━┬┴┯┷")
thin_thick_rounded = TableStyle.from_string("╭─┬─╮││ ┝━┿━┥├─┼─┤╰┴─╯──━━┬┴┯┷")
thin_double = TableStyle.from_string("┌─┬─┐││ ╞═╪═╡├─┼─┤└┴─┘──══┬┴╤╧")
thin_double_rounded = TableStyle.from_string("╭─┬─╮││ ╞═╪═╡├─┼─┤╰┴─╯──══┬┴╤╧")
thick = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫┣━╋━┫┗┻━┛━━━━┳┻┳┻")
thick_box = TableStyle.from_string("┏━┳┳┓┃┃┃┣━╋╋┫┣━╋╋┫┗┻┻┛┳┻┳┻┳┻┳┻")
thick_compact = TableStyle.from_string("┏━┳━┓┃┃ ┣━╋━┫ ┗┻━┛ ━━ ┳┻")
double = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣╠═╬═╣╚╩═╝════╦╩╦╩")
double_box = TableStyle.from_string("╔═╦╦╗║║║╠═╬╬╣╠═╬╬╣╚╩╩╝╦╩╦╩╦╩╦╩")
double_compact = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣ ╚╩═╝ ══ ╦╩")
double_thin_box = TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧╥╨╦╩")
double_thin_compact = TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝ ── ╥╨")
minimalist = TableStyle.from_string(" ─── │ ━━━ ─── ── ──━━┬┴┯┷")
borderless = TableStyle.from_string(" ┃ ━ ━━ ━━")
simple = TableStyle.from_string(" ═ ║ ═ ══ ╦╩")
ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+----++++")
ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++++++++++")
ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+ -- --")
ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+--==--==")
ascii_minimalist = TableStyle.from_string(" --- | === --- -- --==--==")
ascii_borderless = TableStyle.from_string(" | - -- --")
ascii_simple = TableStyle.from_string(" = | = == ==")
ascii_rounded = TableStyle.from_string(r"/===\|| |=|=||-|-|\|=/--==--==")
ascii_rounded_box = TableStyle.from_string(r"/===\||||=||||-|||\||/--==--==")
markdown = TableStyle.from_string(" ||||-||| -- --")
plain = TableStyle.from_string(" " * 30).set(left_and_right_edge="")
Loading