Skip to content
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,
)