diff --git a/README.md b/README.md index d64cc09..48ee314 100644 --- a/README.md +++ b/README.md @@ -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"], @@ -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 @@ -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. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index c06c544..a99bb49 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -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"], @@ -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 ~~~~~~~~~~~~~~~~~~~~~ diff --git a/table2ascii/options.py b/table2ascii/options.py index 10ae1b2..bb2ac6e 100644 --- a/table2ascii/options.py +++ b/table2ascii/options.py @@ -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 diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 8a00449..8835e61 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -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() @@ -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 @@ -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: @@ -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( @@ -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: """ @@ -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 `. @@ -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() diff --git a/tests/test_cell_padding.py b/tests/test_cell_padding.py new file mode 100644 index 0000000..6190053 --- /dev/null +++ b/tests/test_cell_padding.py @@ -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, + )