Skip to content

feat: Ability to align all columns with a single Alignment #91

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 5 commits into from
Dec 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<br/>(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`<br/>(Default: `None`) | First table row seperated by header row separator. Values should support `str()` |
| `body` | `Sequence[Sequence[SupportsStr]]`, `None`<br/>(Default: `None`) | 2D List of rows for the main section of the table. Values should support `str()` |
| `footer` | `Sequence[SupportsStr]`, `None`<br/>(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` |
| `column_widths` | `Sequence[Optional[int]]`, `None`<br/>(Default: `None` / automatic) | List of column widths in characters for each column |
| `alignments` | `Sequence[Alignment]`, `Alignment`, `None`<br/>(Default: `None` / all centered) | Column alignments<br/>(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) |
| `style` | `TableStyle`<br/>(Default: `double_thin_compact`) | Table style to use for the table\* |
| `first_col_heading` | `bool`<br/>(Default: `False`) | Whether to add a heading column separator after the first column |
| `last_col_heading` | `bool`<br/>(Default: `False`) | Whether to add a heading column separator before the last column |
| `cell_padding` | `int`<br/>(Default: `1`) | The minimum number of spaces to add between the cell content and the cell border |
| `use_wcwidth` | `bool`<br/>(Default: `True`) | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width |

[wcwidth]: https://pypi.org/project/wcwidth/

Expand Down
2 changes: 1 addition & 1 deletion docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 24 additions & 1 deletion table2ascii/alignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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],
)

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion table2ascii/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 21 additions & 9 deletions table2ascii/table_to_ascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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.
Expand All @@ -660,8 +662,10 @@ def table2ascii(
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`.
(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
separator. If set to ``0``, the cell content will be flush against the column separator.
Defaults to ``1``.
Expand All @@ -673,6 +677,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
"""
Expand Down
34 changes: 34 additions & 0 deletions tests/test_alignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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