Skip to content

Commit dfbca8c

Browse files
authored
feat: Ability to align all columns with a single Alignment (#91)
1 parent 3e8b7ca commit dfbca8c

File tree

6 files changed

+94
-25
lines changed

6 files changed

+94
-25
lines changed

Diff for: README.md

+13-13
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ output = table2ascii(
123123
body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]],
124124
style=PresetStyle.plain,
125125
cell_padding=0,
126-
alignments=[Alignment.LEFT] * 4,
126+
alignments=Alignment.LEFT,
127127
)
128128

129129
print(output)
@@ -203,18 +203,18 @@ All parameters are optional. At least one of `header`, `body`, and `footer` must
203203

204204
Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information.
205205

206-
| Option | Type | Default | Description |
207-
| :-----------------: | :----------------------------: | :-------------------: | :--------------------------------------------------------------------------------------------------: |
208-
| `header` | `Sequence[SupportsStr]` | `None` | First table row seperated by header row separator. Values should support `str()` |
209-
| `body` | `Sequence[Sequence[Sequence]]` | `None` | 2D List of rows for the main section of the table. Values should support `str()` |
210-
| `footer` | `Sequence[Sequence]` | `None` | Last table row seperated by header row separator. Values should support `str()` |
211-
| `column_widths` | `Sequence[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column |
212-
| `alignments` | `Sequence[Alignment]` | `None` (all centered) | Column alignments<br/>(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) |
213-
| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* |
214-
| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column |
215-
| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column |
216-
| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border |
217-
| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width |
206+
| Option | Supported Types | Description |
207+
| :-----------------: | :-----------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------: |
208+
| `header` | `Sequence[SupportsStr]`, `None`<br/>(Default: `None`) | First table row seperated by header row separator. Values should support `str()` |
209+
| `body` | `Sequence[Sequence[SupportsStr]]`, `None`<br/>(Default: `None`) | 2D List of rows for the main section of the table. Values should support `str()` |
210+
| `footer` | `Sequence[SupportsStr]`, `None`<br/>(Default: `None`) | Last table row seperated by header row separator. Values should support `str()` |
211+
| `column_widths` | `Sequence[Optional[int]]`, `None`<br/>(Default: `None` / automatic) | List of column widths in characters for each column |
212+
| `alignments` | `Sequence[Alignment]`, `Alignment`, `None`<br/>(Default: `None` / all centered) | Column alignments<br/>(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]`) |
213+
| `style` | `TableStyle`<br/>(Default: `double_thin_compact`) | Table style to use for the table\* |
214+
| `first_col_heading` | `bool`<br/>(Default: `False`) | Whether to add a heading column separator after the first column |
215+
| `last_col_heading` | `bool`<br/>(Default: `False`) | Whether to add a heading column separator before the last column |
216+
| `cell_padding` | `int`<br/>(Default: `1`) | The minimum number of spaces to add between the cell content and the cell border |
217+
| `use_wcwidth` | `bool`<br/>(Default: `True`) | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width |
218218

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

Diff for: docs/source/usage.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Use a preset style
109109
body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]],
110110
style=PresetStyle.plain,
111111
cell_padding=0,
112-
alignments=[Alignment.LEFT] * 4,
112+
alignments=Alignment.LEFT,
113113
)
114114
115115
print(output)

Diff for: table2ascii/alignment.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
class Alignment(IntEnum):
55
"""Enum for text alignment types within a table cell
66
7-
Example::
7+
A list of alignment types can be used to align each column individually::
88
99
from table2ascii import Alignment, table2ascii
1010
@@ -15,6 +15,8 @@ class Alignment(IntEnum):
1515
["Cheese", "Dairy", "$10.99", "8.2"],
1616
["Apples", "Produce", "$0.99", "10.00"],
1717
],
18+
# Align the first column to the left, the second to the center,
19+
# the third to the right, and the fourth to the decimal point
1820
alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL],
1921
)
2022
@@ -28,6 +30,27 @@ class Alignment(IntEnum):
2830
╚════════════════════════════════════════╝
2931
\"\"\"
3032
33+
A single alignment type can be used for all columns::
34+
35+
table2ascii(
36+
header=["First Name", "Last Name", "Age"],
37+
body=[
38+
["John", "Smith", 30],
39+
["Jane", "Doe", 28],
40+
],
41+
# Align all columns to the left
42+
alignments=Alignment.LEFT,
43+
)
44+
45+
\"\"\"
46+
╔══════════════════════════════╗
47+
║ First Name Last Name Age ║
48+
╟──────────────────────────────╢
49+
║ John Smith 30 ║
50+
║ Jane Doe 28 ║
51+
╚══════════════════════════════╝
52+
\"\"\"
53+
3154
.. note::
3255
3356
If the :attr:`DECIMAL` alignment type is used, any cell values that are

Diff for: table2ascii/options.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class Options:
1919
first_col_heading: bool
2020
last_col_heading: bool
2121
column_widths: Sequence[int | None] | None
22-
alignments: Sequence[Alignment] | None
22+
alignments: Sequence[Alignment] | Alignment | None
2323
cell_padding: int
2424
style: TableStyle
2525
use_wcwidth: bool

Diff for: table2ascii/table_to_ascii.py

+21-9
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,16 @@ def __init__(
6767
if not header and not body and not footer:
6868
raise NoHeaderBodyOrFooterError()
6969

70-
self.__alignments = options.alignments or [Alignment.CENTER] * self.__columns
70+
alignments = options.alignments if options.alignments is not None else Alignment.CENTER
71+
72+
# if alignments is a single Alignment, convert it to a list of that Alignment
73+
self.__alignments: list[Alignment] = (
74+
[alignments] * self.__columns if isinstance(alignments, Alignment) else list(alignments)
75+
)
7176

7277
# check if alignments specified have a different number of columns
73-
if options.alignments and len(options.alignments) != self.__columns:
74-
raise AlignmentCountMismatchError(options.alignments, self.__columns)
78+
if len(self.__alignments) != self.__columns:
79+
raise AlignmentCountMismatchError(self.__alignments, self.__columns)
7580

7681
# keep track of the number widths and positions of the decimal points for decimal alignment
7782
decimal_widths, decimal_positions = self.__calculate_decimal_widths_and_positions()
@@ -634,16 +639,13 @@ def table2ascii(
634639
first_col_heading: bool = False,
635640
last_col_heading: bool = False,
636641
column_widths: Sequence[int | None] | None = None,
637-
alignments: Sequence[Alignment] | None = None,
642+
alignments: Sequence[Alignment] | Alignment | None = None,
638643
cell_padding: int = 1,
639644
style: TableStyle = PresetStyle.double_thin_compact,
640645
use_wcwidth: bool = True,
641646
) -> str:
642647
"""Convert a 2D Python table to ASCII text
643648
644-
.. versionchanged:: 1.0.0
645-
Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`.
646-
647649
Args:
648650
header: List of column values in the table's header row. All values should be :class:`str`
649651
or support :class:`str` conversion. If not specified, the table will not have a header row.
@@ -660,8 +662,10 @@ def table2ascii(
660662
is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically
661663
sized. Defaults to :py:obj:`None`.
662664
alignments: List of alignments for each column
663-
(ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]``). If not specified or set to
664-
:py:obj:`None`, all columns will be center-aligned. Defaults to :py:obj:`None`.
665+
(ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT, Alignment.DECIMAL]``)
666+
or a single alignment to apply to all columns (ex. ``Alignment.LEFT``).
667+
If not specified or set to :py:obj:`None`, all columns will be center-aligned.
668+
Defaults to :py:obj:`None`.
665669
cell_padding: The minimum number of spaces to add between the cell content and the column
666670
separator. If set to ``0``, the cell content will be flush against the column separator.
667671
Defaults to ``1``.
@@ -673,6 +677,14 @@ def table2ascii(
673677
zero-width space, etc.), whereas :func:`len` determines the width solely based on the number of
674678
characters in the string. Defaults to :py:obj:`True`.
675679
680+
.. versionchanged:: 1.1.0
681+
682+
``alignments`` can now also be specified as a single :class:`Alignment` value to apply to all columns.
683+
684+
.. versionchanged:: 1.0.0
685+
686+
Added the ``use_wcwidth`` parameter defaulting to :py:obj:`True`.
687+
676688
Returns:
677689
The generated ASCII table
678690
"""

Diff for: tests/test_alignments.py

+34
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,37 @@ def test_decimal_alignment():
120120
"╚═════════════╩═══════╧═════════════╧════╧════╧═════════╝"
121121
)
122122
assert text == expected
123+
124+
125+
def test_single_decimal_alignment():
126+
text = t2a(
127+
header=["1.1.1", "G", "Long Header"],
128+
body=[[100.00001, 2, 3.14], [10.0001, 22.0, 2.718]],
129+
alignments=Alignment.DECIMAL,
130+
)
131+
expected = (
132+
"╔════════════════════════════════╗\n"
133+
"║ 1.1.1 G Long Header ║\n"
134+
"╟────────────────────────────────╢\n"
135+
"║ 100.00001 2 3.14 ║\n"
136+
"║ 10.0001 22.0 2.718 ║\n"
137+
"╚════════════════════════════════╝"
138+
)
139+
assert text == expected
140+
141+
142+
def test_single_left_alignment():
143+
text = t2a(
144+
header=["1.1.1", "G", "Long Header"],
145+
body=[[100.00001, 2, 3.14], [10.0001, 22.0, 2.718]],
146+
alignments=Alignment.LEFT,
147+
)
148+
expected = (
149+
"╔════════════════════════════════╗\n"
150+
"║ 1.1.1 G Long Header ║\n"
151+
"╟────────────────────────────────╢\n"
152+
"║ 100.00001 2 3.14 ║\n"
153+
"║ 10.0001 22.0 2.718 ║\n"
154+
"╚════════════════════════════════╝"
155+
)
156+
assert text == expected

0 commit comments

Comments
 (0)