Skip to content

feat: Add cell_padding configurable option #52

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 13 commits into from
Oct 31, 2022
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ print(output)
### Use a preset style

```py
from table2ascii import table2ascii, PresetStyle
from table2ascii import table2ascii, Alignment, PresetStyle

output = table2ascii(
header=["First", "Second", "Third", "Fourth"],
Expand All @@ -111,6 +111,22 @@ print(output)
| 20 | 10 | 20 | 5 |
+----------+----------+----------+----------+
"""

output = table2ascii(
header=["First", "Second", "Third", "Fourth"],
body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]],
style=PresetStyle.plain,
cell_padding=0,
alignments=[Alignment.LEFT] * 4,
)

print(output)

"""
First Second Third Fourth
10 30 40 35
20 10 20 5
"""
```

### Define a custom style
Expand Down Expand Up @@ -159,6 +175,7 @@ All parameters are optional.
| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table |
| `first_col_heading` | `bool` | `False` | Whether to add a heading column seperator after the first column |
| `last_col_heading` | `bool` | `False` | Whether to add a heading column seperator before the last column |
| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border. |

See the [API Reference](https://table2ascii.readthedocs.io/en/latest/api.html) for more info.

Expand Down
18 changes: 17 additions & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Use a preset style

.. code:: py

from table2ascii import table2ascii, PresetStyle
from table2ascii import table2ascii, Alignment, PresetStyle

output = table2ascii(
header=["First", "Second", "Third", "Fourth"],
Expand All @@ -100,6 +100,22 @@ Use a preset style
+----------+----------+----------+----------+
"""

output = table2ascii(
header=["First", "Second", "Third", "Fourth"],
body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]],
style=PresetStyle.plain,
cell_padding=0,
alignments=[Alignment.LEFT] * 4,
)

print(output)

"""
First Second Third Fourth
10 30 40 35
20 10 20 5
"""

Define a custom style
~~~~~~~~~~~~~~~~~~~~~

Expand Down
1 change: 1 addition & 0 deletions table2ascii/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class Options:
last_col_heading: bool
column_widths: Optional[List[Optional[int]]]
alignments: Optional[List[Alignment]]
cell_padding: int
style: TableStyle
26 changes: 19 additions & 7 deletions table2ascii/table_to_ascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__(
self.__style = options.style
self.__first_col_heading = options.first_col_heading
self.__last_col_heading = options.last_col_heading
self.__cell_padding = options.cell_padding

# calculate number of columns
self.__columns = self.__count_columns()
Expand Down Expand Up @@ -71,6 +72,10 @@ def __init__(
if options.alignments and len(options.alignments) != self.__columns:
raise ValueError("Length of `alignments` list must equal the number of columns")

# check if the cell padding is valid
if self.__cell_padding < 0:
raise ValueError("Cell padding must be greater than or equal to 0")

def __count_columns(self) -> int:
"""
Get the number of columns in the table based on the
Expand Down Expand Up @@ -108,8 +113,8 @@ def widest_line(value: SupportsStr) -> int:
header_size = widest_line(self.__header[i]) if self.__header else 0
body_size = max(widest_line(row[i]) for row in self.__body) if self.__body else 0
footer_size = widest_line(self.__footer[i]) if self.__footer else 0
# get the max and add 2 for padding each side with a space
column_widths.append(max(header_size, body_size, footer_size) + 2)
# get the max and add 2 for padding each side with a space depending on cell padding
column_widths.append(max(header_size, body_size, footer_size) + self.__cell_padding * 2)
return column_widths

def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> str:
Expand All @@ -125,17 +130,19 @@ def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> st
The padded text
"""
text = str(cell_value)
padding = " " * self.__cell_padding
padded_text = f"{padding}{text}{padding}"
if alignment == Alignment.LEFT:
# pad with spaces on the end
return f" {text} " + (" " * (width - len(text) - 2))
return padded_text + (" " * (width - len(padded_text)))
if alignment == Alignment.CENTER:
# pad with spaces, half on each side
before = " " * floor((width - len(text) - 2) / 2)
after = " " * ceil((width - len(text) - 2) / 2)
return before + f" {text} " + after
before = " " * floor((width - len(padded_text)) / 2)
after = " " * ceil((width - len(padded_text)) / 2)
return before + padded_text + after
if alignment == Alignment.RIGHT:
# pad with spaces at the beginning
return (" " * (width - len(text) - 2)) + f" {text} "
return (" " * (width - len(padded_text))) + padded_text
raise ValueError(f"The value '{alignment}' is not valid for alignment.")

def __row_to_ascii(
Expand Down Expand Up @@ -318,6 +325,7 @@ def table2ascii(
last_col_heading: bool = False,
column_widths: Optional[List[Optional[int]]] = None,
alignments: Optional[List[Alignment]] = None,
cell_padding: int = 1,
style: TableStyle = PresetStyle.double_thin_compact,
) -> str:
"""
Expand All @@ -341,6 +349,9 @@ def table2ascii(
alignments: List of alignments for each column
(ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]``). If not specified or set to
:py:obj:`None`, all columns will be center-aligned. Defaults to :py:obj:`None`.
cell_padding: The minimum number of spaces to add between the cell content and the column
separator. If set to ``0``, the cell content will be flush against the column separator.
Defaults to ``1``.
style: Table style to use for styling (preset styles can be imported).
Defaults to :ref:`PresetStyle.double_thin_compact <PresetStyle.double_thin_compact>`.

Expand All @@ -356,6 +367,7 @@ def table2ascii(
last_col_heading=last_col_heading,
column_widths=column_widths,
alignments=alignments,
cell_padding=cell_padding,
style=style,
),
).to_ascii()
82 changes: 82 additions & 0 deletions tests/test_cell_padding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import pytest

from table2ascii import Alignment, table2ascii as t2a


def test_without_cell_padding():
text = t2a(
header=["#", "G", "H", "R", "S"],
body=[[1, 2, 3, 4, 5]],
footer=["A", "B", 1, 2, 3],
first_col_heading=True,
cell_padding=0,
)
expected = (
"╔═╦═══════╗\n"
"║#║G H R S║\n"
"╟─╫───────╢\n"
"║1║2 3 4 5║\n"
"╟─╫───────╢\n"
"║A║B 1 2 3║\n"
"╚═╩═══════╝"
)
assert text == expected


def test_column_width_and_alignment_without_cell_padding():
text = t2a(
header=["#", "G", "H", "R", "S"],
body=[[1, 2, 3, 4, 5]],
footer=["A", "B", 1, 2, 3],
column_widths=[4, 8, 5, 4, 5],
alignments=[
Alignment.LEFT,
Alignment.CENTER,
Alignment.RIGHT,
Alignment.LEFT,
Alignment.RIGHT,
],
first_col_heading=True,
cell_padding=0,
)
expected = (
"╔════╦═════════════════════════╗\n"
"║# ║ G H R S║\n"
"╟────╫─────────────────────────╢\n"
"║1 ║ 2 3 4 5║\n"
"╟────╫─────────────────────────╢\n"
"║A ║ B 1 2 3║\n"
"╚════╩═════════════════════════╝"
)
assert text == expected


def test_cell_padding_more_than_one():
text = t2a(
header=["#", "G", "H", "R", "S"],
body=[[1, 2, 3, 4, 5]],
footer=["A", "B", 1, 2, 3],
first_col_heading=True,
cell_padding=2,
)
expected = (
"╔═════╦═══════════════════════╗\n"
"║ # ║ G H R S ║\n"
"╟─────╫───────────────────────╢\n"
"║ 1 ║ 2 3 4 5 ║\n"
"╟─────╫───────────────────────╢\n"
"║ A ║ B 1 2 3 ║\n"
"╚═════╩═══════════════════════╝"
)
assert text == expected


def test_negative_cell_padding():
with pytest.raises(ValueError):
t2a(
header=["#", "G", "H", "R", "S"],
body=[[1, 2, 3, 4, 5]],
footer=["A", "B", 1, 2, 3],
first_col_heading=True,
cell_padding=-1,
)