From e8f85cb9ede7ee0d85017be425a1cb7772270e84 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 15:22:43 -0700 Subject: [PATCH 1/5] feat: Can align all columns with one Alignment --- README.md | 26 +++++++++++++------------- table2ascii/alignment.py | 25 ++++++++++++++++++++++++- table2ascii/options.py | 2 +- table2ascii/table_to_ascii.py | 31 +++++++++++++++++++++---------- tests/test_alignments.py | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index de9e288..ebd4973 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ output = table2ascii( body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], style=PresetStyle.plain, cell_padding=0, - alignments=[Alignment.LEFT] * 4, + alignments=Alignment.LEFT, ) print(output) @@ -203,18 +203,18 @@ All parameters are optional. At least one of `header`, `body`, and `footer` must Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information. -| Option | Type | Default | Description | -| :-----------------: | :----------------------------: | :-------------------: | :--------------------------------------------------------------------------------------------------: | -| `header` | `Sequence[SupportsStr]` | `None` | First table row seperated by header row separator. Values should support `str()` | -| `body` | `Sequence[Sequence[Sequence]]` | `None` | 2D List of rows for the main section of the table. Values should support `str()` | -| `footer` | `Sequence[Sequence]` | `None` | Last table row seperated by header row separator. Values should support `str()` | -| `column_widths` | `Sequence[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column | -| `alignments` | `Sequence[Alignment]` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | -| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* | -| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column | -| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column | -| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border | -| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | +| Option | Supported Types | Default | Description | +| :-----------------: | :----------------------------------------: | :-------------------: | :--------------------------------------------------------------------------------------------------: | +| `header` | `Sequence[SupportsStr]`, `None` | `None` | First table row seperated by header row separator. Values should support `str()` | +| `body` | `Sequence[Sequence[SupportsStr]]`, `None` | `None` | 2D List of rows for the main section of the table. Values should support `str()` | +| `footer` | `Sequence[SupportsStr]`, `None` | `None` | Last table row seperated by header row separator. Values should support `str()` | +| `column_widths` | `Sequence[Optional[int]]`, `None` | `None` (automatic) | List of column widths in characters for each column | +| `alignments` | `Sequence[Alignment]`, `Alignment`, `None` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | +| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* | +| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column | +| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column | +| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border | +| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | [wcwidth]: https://pypi.org/project/wcwidth/ diff --git a/table2ascii/alignment.py b/table2ascii/alignment.py index 2ae487f..041d167 100644 --- a/table2ascii/alignment.py +++ b/table2ascii/alignment.py @@ -4,7 +4,7 @@ class Alignment(IntEnum): """Enum for text alignment types within a table cell - Example:: + A list of alignment types can be used to align each column individually:: from table2ascii import Alignment, table2ascii @@ -15,6 +15,8 @@ class Alignment(IntEnum): ["Cheese", "Dairy", "$10.99", "8.2"], ["Apples", "Produce", "$0.99", "10.00"], ], + # Align the first column to the left, the second to the center, + # the third to the right, and the fourth to the decimal point alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL], ) @@ -28,6 +30,27 @@ class Alignment(IntEnum): ╚════════════════════════════════════════╝ \"\"\" + A single alignment type can be used for all columns:: + + table2ascii( + header=["First Name", "Last Name", "Age"], + body=[ + ["John", "Smith", 30], + ["Jane", "Doe", 28], + ], + # Align all columns to the left + alignments=Alignment.LEFT, + ) + + \"\"\" + ╔══════════════════════════════╗ + ║ First Name Last Name Age ║ + ╟──────────────────────────────╢ + ║ John Smith 30 ║ + ║ Jane Doe 28 ║ + ╚══════════════════════════════╝ + \"\"\" + .. note:: If the :attr:`DECIMAL` alignment type is used, any cell values that are diff --git a/table2ascii/options.py b/table2ascii/options.py index 36f6ee0..e88bd44 100644 --- a/table2ascii/options.py +++ b/table2ascii/options.py @@ -19,7 +19,7 @@ class Options: first_col_heading: bool last_col_heading: bool column_widths: Sequence[int | None] | None - alignments: Sequence[Alignment] | None + alignments: Sequence[Alignment] | Alignment | None cell_padding: int style: TableStyle use_wcwidth: bool diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 0510bd1..a380b53 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -67,11 +67,16 @@ def __init__( if not header and not body and not footer: raise NoHeaderBodyOrFooterError() - self.__alignments = options.alignments or [Alignment.CENTER] * self.__columns + alignments = options.alignments if options.alignments is not None else Alignment.CENTER + + # if alignments is a single Alignment, convert it to a list of that Alignment + self.__alignments: list[Alignment] = ( + [alignments] * self.__columns if isinstance(alignments, Alignment) else list(alignments) + ) # check if alignments specified have a different number of columns - if options.alignments and len(options.alignments) != self.__columns: - raise AlignmentCountMismatchError(options.alignments, self.__columns) + if len(self.__alignments) != self.__columns: + raise AlignmentCountMismatchError(self.__alignments, self.__columns) # keep track of the number widths and positions of the decimal points for decimal alignment decimal_widths, decimal_positions = self.__calculate_decimal_widths_and_positions() @@ -634,16 +639,13 @@ def table2ascii( first_col_heading: bool = False, last_col_heading: bool = False, column_widths: Sequence[int | None] | None = None, - alignments: Sequence[Alignment] | None = None, + alignments: Sequence[Alignment] | Alignment | None = None, cell_padding: int = 1, style: TableStyle = PresetStyle.double_thin_compact, use_wcwidth: bool = True, ) -> str: """Convert a 2D Python table to ASCII text - .. versionchanged:: 1.0.0 - Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`. - Args: header: List of column values in the table's header row. All values should be :class:`str` or support :class:`str` conversion. If not specified, the table will not have a header row. @@ -659,9 +661,10 @@ def table2ascii( indicates that the column width should be determined automatically. If :py:obj:`None` is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically sized. Defaults to :py:obj:`None`. - 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`. + alignments: List of alignments for each column or a single alignment to apply to all columns + (ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]``). + 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``. @@ -673,6 +676,14 @@ def table2ascii( zero-width space, etc.), whereas :func:`len` determines the width solely based on the number of characters in the string. Defaults to :py:obj:`True`. + .. versionchanged:: 1.1.0 + + ``alignments`` can now also be specified as a single :class:`Alignment` value to apply to all columns. + + .. versionchanged:: 1.0.0 + + Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`. + Returns: The generated ASCII table """ diff --git a/tests/test_alignments.py b/tests/test_alignments.py index 107c32b..ff684e1 100644 --- a/tests/test_alignments.py +++ b/tests/test_alignments.py @@ -120,3 +120,37 @@ def test_decimal_alignment(): "╚═════════════╩═══════╧═════════════╧════╧════╧═════════╝" ) assert text == expected + + +def test_single_decimal_alignment(): + text = t2a( + header=["1.1.1", "G", "Long Header"], + body=[[100.00001, 2, 3.14], [10.0001, 22.0, 2.718]], + alignments=Alignment.DECIMAL, + ) + expected = ( + "╔════════════════════════════════╗\n" + "║ 1.1.1 G Long Header ║\n" + "╟────────────────────────────────╢\n" + "║ 100.00001 2 3.14 ║\n" + "║ 10.0001 22.0 2.718 ║\n" + "╚════════════════════════════════╝" + ) + assert text == expected + + +def test_single_left_alignment(): + text = t2a( + header=["1.1.1", "G", "Long Header"], + body=[[100.00001, 2, 3.14], [10.0001, 22.0, 2.718]], + alignments=Alignment.LEFT, + ) + expected = ( + "╔════════════════════════════════╗\n" + "║ 1.1.1 G Long Header ║\n" + "╟────────────────────────────────╢\n" + "║ 100.00001 2 3.14 ║\n" + "║ 10.0001 22.0 2.718 ║\n" + "╚════════════════════════════════╝" + ) + assert text == expected From 2277a8d0dbcaef70446d95485c8d5a77e90524b3 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 15:30:38 -0700 Subject: [PATCH 2/5] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ebd4973..0e6dfdc 100644 --- a/README.md +++ b/README.md @@ -203,18 +203,18 @@ All parameters are optional. At least one of `header`, `body`, and `footer` must Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information. -| Option | Supported Types | Default | Description | -| :-----------------: | :----------------------------------------: | :-------------------: | :--------------------------------------------------------------------------------------------------: | -| `header` | `Sequence[SupportsStr]`, `None` | `None` | First table row seperated by header row separator. Values should support `str()` | -| `body` | `Sequence[Sequence[SupportsStr]]`, `None` | `None` | 2D List of rows for the main section of the table. Values should support `str()` | -| `footer` | `Sequence[SupportsStr]`, `None` | `None` | Last table row seperated by header row separator. Values should support `str()` | -| `column_widths` | `Sequence[Optional[int]]`, `None` | `None` (automatic) | List of column widths in characters for each column | -| `alignments` | `Sequence[Alignment]`, `Alignment`, `None` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | -| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* | -| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column | -| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column | -| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border | -| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | +| Option | Supported Types | Description | +| :-----------------: | :--------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: | +| `header` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | First table row seperated by header row separator. Values should support `str()` | +| `body` | `Sequence[Sequence[SupportsStr]]`, `None`
(Default: `None`) | 2D List of rows for the main section of the table. Values should support `str()` | +| `footer` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` | +| `column_widths` | `Sequence[Optional[int]]`, `None`
(Default: automatic) | List of column widths in characters for each column | +| `alignments` | `Sequence[Alignment]`, `Alignment`, `None`
(Default: all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | +| `style` | `TableStyle`
(Default: `PresetStyle.double_thin_compact`) | Table style to use for the table\* | +| `first_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator after the first column | +| `last_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator before the last column | +| `cell_padding` | `int`
(Default: `1`) | The minimum number of spaces to add between the cell content and the cell border | +| `use_wcwidth` | `bool`
(Default: `True`) | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | [wcwidth]: https://pypi.org/project/wcwidth/ From 7da3a7899af9f0e6443c02fe88bf1ad321f9d002 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 15:31:25 -0700 Subject: [PATCH 3/5] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0e6dfdc..ca0e1c6 100644 --- a/README.md +++ b/README.md @@ -203,18 +203,18 @@ All parameters are optional. At least one of `header`, `body`, and `footer` must Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information. -| Option | Supported Types | Description | -| :-----------------: | :--------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: | -| `header` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | First table row seperated by header row separator. Values should support `str()` | -| `body` | `Sequence[Sequence[SupportsStr]]`, `None`
(Default: `None`) | 2D List of rows for the main section of the table. Values should support `str()` | -| `footer` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` | -| `column_widths` | `Sequence[Optional[int]]`, `None`
(Default: automatic) | List of column widths in characters for each column | -| `alignments` | `Sequence[Alignment]`, `Alignment`, `None`
(Default: all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | -| `style` | `TableStyle`
(Default: `PresetStyle.double_thin_compact`) | Table style to use for the table\* | -| `first_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator after the first column | -| `last_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator before the last column | -| `cell_padding` | `int`
(Default: `1`) | The minimum number of spaces to add between the cell content and the cell border | -| `use_wcwidth` | `bool`
(Default: `True`) | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | +| Option | Supported Types | Description | +| :-----------------: | :-----------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: | +| `header` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | First table row seperated by header row separator. Values should support `str()` | +| `body` | `Sequence[Sequence[SupportsStr]]`, `None`
(Default: `None`) | 2D List of rows for the main section of the table. Values should support `str()` | +| `footer` | `Sequence[SupportsStr]`, `None`
(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` | +| `column_widths` | `Sequence[Optional[int]]`, `None`
(Default: `None` / automatic) | List of column widths in characters for each column | +| `alignments` | `Sequence[Alignment]`, `Alignment`, `None`
(Default: `None` / all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) | +| `style` | `TableStyle`
(Default: `double_thin_compact`) | Table style to use for the table\* | +| `first_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator after the first column | +| `last_col_heading` | `bool`
(Default: `False`) | Whether to add a heading column separator before the last column | +| `cell_padding` | `int`
(Default: `1`) | The minimum number of spaces to add between the cell content and the cell border | +| `use_wcwidth` | `bool`
(Default: `True`) | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width | [wcwidth]: https://pypi.org/project/wcwidth/ From 68ad45fa4bbd9c936e7aec54d1227422aae74d61 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 15:33:36 -0700 Subject: [PATCH 4/5] Update docs --- table2ascii/table_to_ascii.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index a380b53..46603d7 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -661,8 +661,9 @@ def table2ascii( indicates that the column width should be determined automatically. If :py:obj:`None` is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically sized. Defaults to :py:obj:`None`. - alignments: List of alignments for each column or a single alignment to apply to all columns - (ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]``). + alignments: List of alignments for each column + (ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]``) + or a single alignment to apply to all columns (ex. ``Alignment.LEFT``). 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 From 592053a0391845516177a38a91310f2c85e04d11 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Wed, 28 Dec 2022 15:34:04 -0700 Subject: [PATCH 5/5] Update usage.rst --- docs/source/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 823c23e..d1eb820 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -109,7 +109,7 @@ Use a preset style body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], style=PresetStyle.plain, cell_padding=0, - alignments=[Alignment.LEFT] * 4, + alignments=Alignment.LEFT, ) print(output)