table2ascii 1.1.0__tar.gz → 1.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {table2ascii-1.1.0/table2ascii.egg-info → table2ascii-1.1.2}/PKG-INFO +1 -1
- {table2ascii-1.1.0 → table2ascii-1.1.2}/pyproject.toml +3 -3
- table2ascii-1.1.2/setup.py +51 -0
- table2ascii-1.1.2/table2ascii/__init__.py +56 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/annotations.py +2 -1
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/exceptions.py +23 -18
- table2ascii-1.1.2/table2ascii/py.typed +1 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/table_style.py +1 -1
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/table_to_ascii.py +10 -6
- {table2ascii-1.1.0 → table2ascii-1.1.2/table2ascii.egg-info}/PKG-INFO +1 -1
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii.egg-info/SOURCES.txt +9 -1
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii.egg-info/requires.txt +2 -2
- table2ascii-1.1.2/tests/test_alignments.py +192 -0
- table2ascii-1.1.2/tests/test_cell_padding.py +83 -0
- table2ascii-1.1.2/tests/test_column_widths.py +110 -0
- table2ascii-1.1.2/tests/test_convert.py +307 -0
- table2ascii-1.1.2/tests/test_heading_cols.py +113 -0
- table2ascii-1.1.2/tests/test_merge.py +175 -0
- table2ascii-1.1.2/tests/test_styles.py +113 -0
- table2ascii-1.1.0/setup.py +0 -4
- table2ascii-1.1.0/table2ascii/__init__.py +0 -26
- {table2ascii-1.1.0 → table2ascii-1.1.2}/LICENSE +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/README.md +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/setup.cfg +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/alignment.py +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/merge.py +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/options.py +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii/preset_style.py +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii.egg-info/dependency_links.txt +0 -0
- {table2ascii-1.1.0 → table2ascii-1.1.2}/table2ascii.egg-info/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "table2ascii"
|
|
8
|
-
version = "1.1.
|
|
8
|
+
version = "1.1.2"
|
|
9
9
|
authors = [{name = "Jonah Lawrence", email = "jonah@freshidea.com"}]
|
|
10
10
|
description = "Convert 2D Python lists into Unicode/ASCII tables"
|
|
11
11
|
readme = "README.md"
|
|
@@ -54,8 +54,8 @@ docs = [
|
|
|
54
54
|
"sphinx-book-theme==0.3.3",
|
|
55
55
|
]
|
|
56
56
|
dev = [
|
|
57
|
-
"mypy>=0.982,<
|
|
58
|
-
"pre-commit>=2.0.0,<
|
|
57
|
+
"mypy>=0.982,<2",
|
|
58
|
+
"pre-commit>=2.0.0,<4",
|
|
59
59
|
"pyright>=1.0.0,<2",
|
|
60
60
|
"pytest>=6.0.0,<8",
|
|
61
61
|
"slotscheck>=0.1.0,<1",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# /usr/bin/env python
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from setuptools import setup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_name():
|
|
8
|
+
name = ""
|
|
9
|
+
with open("pyproject.toml") as f:
|
|
10
|
+
name = re.search(r'^name = ["\']([^"\']*)["\']', f.read(), re.M)
|
|
11
|
+
if not name:
|
|
12
|
+
raise RuntimeError("name is not set")
|
|
13
|
+
return name.group(1)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_version():
|
|
17
|
+
version = ""
|
|
18
|
+
with open("pyproject.toml") as f:
|
|
19
|
+
version = re.search(r'^version = ["\']([^"\']*)["\']', f.read(), re.M)
|
|
20
|
+
if not version:
|
|
21
|
+
raise RuntimeError("version is not set")
|
|
22
|
+
return version.group(1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_dependencies():
|
|
26
|
+
with open("pyproject.toml") as f:
|
|
27
|
+
dependency_match = re.search(r"^dependencies = \[([\s\S]*?)\]", f.read(), re.M)
|
|
28
|
+
if not dependency_match or not dependency_match.group(1):
|
|
29
|
+
return []
|
|
30
|
+
return [
|
|
31
|
+
dependency.strip().strip(",").strip('"')
|
|
32
|
+
for dependency in dependency_match.group(1).split("\n")
|
|
33
|
+
if dependency
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# check if pyproject.toml can be used to install dependencies and set the version
|
|
39
|
+
setup(
|
|
40
|
+
packages=[get_name()],
|
|
41
|
+
package_data={get_name(): ["py.typed"]},
|
|
42
|
+
)
|
|
43
|
+
except Exception:
|
|
44
|
+
# fallback for old versions of pip/setuptools
|
|
45
|
+
setup(
|
|
46
|
+
name=get_name(),
|
|
47
|
+
packages=[get_name()],
|
|
48
|
+
package_data={get_name(): ["py.typed"]},
|
|
49
|
+
version=get_version(),
|
|
50
|
+
install_requires=get_dependencies(),
|
|
51
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
table2ascii - Library for converting 2D Python lists to fancy ASCII/Unicode tables
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .alignment import Alignment
|
|
8
|
+
from .annotations import SupportsStr
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
AlignmentCountMismatchError,
|
|
11
|
+
BodyColumnCountMismatchError,
|
|
12
|
+
ColumnCountMismatchError,
|
|
13
|
+
ColumnWidthsCountMismatchError,
|
|
14
|
+
ColumnWidthTooSmallError,
|
|
15
|
+
FooterColumnCountMismatchError,
|
|
16
|
+
InvalidAlignmentError,
|
|
17
|
+
InvalidCellPaddingError,
|
|
18
|
+
InvalidColumnWidthError,
|
|
19
|
+
Table2AsciiError,
|
|
20
|
+
TableOptionError,
|
|
21
|
+
TableStyleTooLongError,
|
|
22
|
+
TableStyleTooShortWarning,
|
|
23
|
+
)
|
|
24
|
+
from .merge import Merge
|
|
25
|
+
from .preset_style import PresetStyle
|
|
26
|
+
from .table_style import TableStyle
|
|
27
|
+
from .table_to_ascii import table2ascii
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING or sys.version_info >= (3, 8):
|
|
30
|
+
from importlib import metadata
|
|
31
|
+
else:
|
|
32
|
+
import importlib_metadata as metadata
|
|
33
|
+
|
|
34
|
+
__version__ = metadata.version(__name__)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Alignment",
|
|
38
|
+
"Merge",
|
|
39
|
+
"PresetStyle",
|
|
40
|
+
"TableStyle",
|
|
41
|
+
"table2ascii",
|
|
42
|
+
"AlignmentCountMismatchError",
|
|
43
|
+
"BodyColumnCountMismatchError",
|
|
44
|
+
"ColumnCountMismatchError",
|
|
45
|
+
"ColumnWidthsCountMismatchError",
|
|
46
|
+
"ColumnWidthTooSmallError",
|
|
47
|
+
"FooterColumnCountMismatchError",
|
|
48
|
+
"InvalidAlignmentError",
|
|
49
|
+
"InvalidCellPaddingError",
|
|
50
|
+
"InvalidColumnWidthError",
|
|
51
|
+
"Table2AsciiError",
|
|
52
|
+
"TableOptionError",
|
|
53
|
+
"TableStyleTooLongError",
|
|
54
|
+
"TableStyleTooShortWarning",
|
|
55
|
+
"SupportsStr",
|
|
56
|
+
]
|
|
@@ -10,8 +10,9 @@ else:
|
|
|
10
10
|
|
|
11
11
|
@runtime_checkable
|
|
12
12
|
class SupportsStr(Protocol):
|
|
13
|
-
"""An ABC with one abstract method __str__
|
|
13
|
+
"""An abstract base class (ABC) with one abstract method :meth:`__str__`"""
|
|
14
14
|
|
|
15
15
|
@abstractmethod
|
|
16
16
|
def __str__(self) -> str:
|
|
17
|
+
"""Return a string representation of the object"""
|
|
17
18
|
pass
|
|
@@ -40,8 +40,9 @@ class FooterColumnCountMismatchError(ColumnCountMismatchError):
|
|
|
40
40
|
This class is a subclass of :class:`ColumnCountMismatchError`.
|
|
41
41
|
|
|
42
42
|
Attributes:
|
|
43
|
-
footer (Sequence[SupportsStr]):
|
|
44
|
-
|
|
43
|
+
footer (:class:`Sequence <collections.abc.Sequence>`\ [:class:`SupportsStr`]):
|
|
44
|
+
The footer that caused the error
|
|
45
|
+
expected_columns (:class:`int`): The number of columns that were expected
|
|
45
46
|
"""
|
|
46
47
|
|
|
47
48
|
def __init__(self, footer: Sequence[SupportsStr], expected_columns: int):
|
|
@@ -63,9 +64,11 @@ class BodyColumnCountMismatchError(ColumnCountMismatchError):
|
|
|
63
64
|
This class is a subclass of :class:`ColumnCountMismatchError`.
|
|
64
65
|
|
|
65
66
|
Attributes:
|
|
66
|
-
body (Sequence[Sequence[SupportsStr]]):
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
body (:class:`Sequence <collections.abc.Sequence>`\ [\ :class:`Sequence <collections.abc.Sequence>`\ [:class:`SupportsStr`]]):
|
|
68
|
+
The body that caused the error
|
|
69
|
+
expected_columns (:class:`int`): The number of columns that were expected
|
|
70
|
+
first_invalid_row (:class:`Sequence <collections.abc.Sequence>`\ [:class:`SupportsStr`]):
|
|
71
|
+
The first row with an invalid column count
|
|
69
72
|
"""
|
|
70
73
|
|
|
71
74
|
def __init__(self, body: Sequence[Sequence[SupportsStr]], expected_columns: int):
|
|
@@ -90,8 +93,9 @@ class AlignmentCountMismatchError(ColumnCountMismatchError):
|
|
|
90
93
|
This class is a subclass of :class:`ColumnCountMismatchError`.
|
|
91
94
|
|
|
92
95
|
Attributes:
|
|
93
|
-
alignments (Sequence[Alignment]):
|
|
94
|
-
|
|
96
|
+
alignments (:class:`Sequence <collections.abc.Sequence>`\ [:class:`Alignment`]):
|
|
97
|
+
The alignments that caused the error
|
|
98
|
+
expected_columns (:class:`int`): The number of columns that were expected
|
|
95
99
|
"""
|
|
96
100
|
|
|
97
101
|
def __init__(self, alignments: Sequence[Alignment], expected_columns: int):
|
|
@@ -113,8 +117,9 @@ class ColumnWidthsCountMismatchError(ColumnCountMismatchError):
|
|
|
113
117
|
This class is a subclass of :class:`ColumnCountMismatchError`.
|
|
114
118
|
|
|
115
119
|
Attributes:
|
|
116
|
-
column_widths (Sequence[Optional[int]]):
|
|
117
|
-
|
|
120
|
+
column_widths (:class:`Sequence <collections.abc.Sequence>`\ [:data:`Optional <typing.Optional>`\ [:class:`int`]]):
|
|
121
|
+
The column widths that caused the error
|
|
122
|
+
expected_columns (:class:`int`): The number of columns that were expected
|
|
118
123
|
"""
|
|
119
124
|
|
|
120
125
|
def __init__(self, column_widths: Sequence[int | None], expected_columns: int):
|
|
@@ -148,7 +153,7 @@ class InvalidCellPaddingError(TableOptionError):
|
|
|
148
153
|
This class is a subclass of :class:`TableOptionError`.
|
|
149
154
|
|
|
150
155
|
Attributes:
|
|
151
|
-
padding (int): The padding that caused the error
|
|
156
|
+
padding (:class:`int`): The padding that caused the error
|
|
152
157
|
"""
|
|
153
158
|
|
|
154
159
|
def __init__(self, padding: int):
|
|
@@ -169,9 +174,9 @@ class ColumnWidthTooSmallError(TableOptionError):
|
|
|
169
174
|
This class is a subclass of :class:`TableOptionError`.
|
|
170
175
|
|
|
171
176
|
Attributes:
|
|
172
|
-
column_index (int): The index of the column that caused the error
|
|
173
|
-
column_width (int): The column width that caused the error
|
|
174
|
-
min_width (int): The minimum width that is allowed
|
|
177
|
+
column_index (:class:`int`): The index of the column that caused the error
|
|
178
|
+
column_width (:class:`int`): The column width that caused the error
|
|
179
|
+
min_width (:class:`int`): The minimum width that is allowed
|
|
175
180
|
"""
|
|
176
181
|
|
|
177
182
|
def __init__(self, column_index: int, column_width: int, min_width: int | None = None):
|
|
@@ -208,7 +213,7 @@ class InvalidAlignmentError(TableOptionError):
|
|
|
208
213
|
This class is a subclass of :class:`TableOptionError`.
|
|
209
214
|
|
|
210
215
|
Attributes:
|
|
211
|
-
alignment (Any): The alignment value that caused the error
|
|
216
|
+
alignment (:data:`Any <typing.Any>`): The alignment value that caused the error
|
|
212
217
|
"""
|
|
213
218
|
|
|
214
219
|
def __init__(self, alignment: Any):
|
|
@@ -230,8 +235,8 @@ class TableStyleTooLongError(Table2AsciiError, ValueError):
|
|
|
230
235
|
This class is a subclass of :class:`Table2AsciiError` and :class:`ValueError`.
|
|
231
236
|
|
|
232
237
|
Attributes:
|
|
233
|
-
string (str): The string that caused the error
|
|
234
|
-
max_characters (int): The maximum number of characters that are allowed
|
|
238
|
+
string (:class:`str`): The string that caused the error
|
|
239
|
+
max_characters (:class:`int`): The maximum number of characters that are allowed
|
|
235
240
|
"""
|
|
236
241
|
|
|
237
242
|
def __init__(self, string: str, max_characters: int):
|
|
@@ -256,8 +261,8 @@ class TableStyleTooShortWarning(UserWarning):
|
|
|
256
261
|
It can be silenced using :func:`warnings.filterwarnings`.
|
|
257
262
|
|
|
258
263
|
Attributes:
|
|
259
|
-
string (str): The string that caused the warning
|
|
260
|
-
max_characters (int): The number of characters that :class:`TableStyle` accepts
|
|
264
|
+
string (:class:`str`): The string that caused the warning
|
|
265
|
+
max_characters (:class:`int`): The number of characters that :class:`TableStyle` accepts
|
|
261
266
|
"""
|
|
262
267
|
|
|
263
268
|
def __init__(self, string: str, max_characters: int):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561. The table2ascii package uses inline types.
|
|
@@ -145,7 +145,7 @@ class TableStyle:
|
|
|
145
145
|
|
|
146
146
|
Example::
|
|
147
147
|
|
|
148
|
-
TableStyle().set(
|
|
148
|
+
TableStyle.from_string("~" * 30).set(left_and_right_edge="", col_sep="")
|
|
149
149
|
"""
|
|
150
150
|
for key, value in kwargs.items():
|
|
151
151
|
setattr(self, key, value)
|
|
@@ -677,23 +677,27 @@ def table2ascii(
|
|
|
677
677
|
"""Convert a 2D Python table to ASCII text
|
|
678
678
|
|
|
679
679
|
Args:
|
|
680
|
-
header:
|
|
680
|
+
header (:data:`Optional <typing.Optional>`\ [:class:`Sequence <collections.abc.Sequence>`\ [:class:`SupportsStr`]]):
|
|
681
|
+
List of column values in the table's header row. All values should be :class:`str`
|
|
681
682
|
or support :class:`str` conversion. If not specified, the table will not have a header row.
|
|
682
|
-
body:
|
|
683
|
+
body (:data:`Optional <typing.Optional>`\ [:class:`Sequence <collections.abc.Sequence>`\ [:class:`Sequence <collections.abc.Sequence>`\ [:class:`SupportsStr`]]]):
|
|
684
|
+
2-dimensional list of values in the table's body. All values should be :class:`str`
|
|
683
685
|
or support :class:`str` conversion. If not specified, the table will not have a body.
|
|
684
|
-
footer:
|
|
686
|
+
footer (:data:`Optional <typing.Optional>`\ [:class:`Sequence <collections.abc.Sequence>`\ [:class:`SupportsStr`]]):
|
|
687
|
+
List of column values in the table's footer row. All values should be :class:`str`
|
|
685
688
|
or support :class:`str` conversion. If not specified, the table will not have a footer row.
|
|
686
689
|
first_col_heading: Whether to add a header column separator after the first column.
|
|
687
690
|
Defaults to :py:obj:`False`.
|
|
688
691
|
last_col_heading: Whether to add a header column separator before the last column.
|
|
689
692
|
Defaults to :py:obj:`False`.
|
|
690
|
-
column_widths:
|
|
693
|
+
column_widths (:data:`Optional <typing.Optional>`\ [:class:`Sequence <collections.abc.Sequence>`\ [:data:`Optional <typing.Optional>`\ [:class:`int`]]]):
|
|
694
|
+
List of widths in characters for each column. Any value of :py:obj:`None`
|
|
691
695
|
indicates that the column width should be determined automatically. If :py:obj:`None`
|
|
692
696
|
is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically
|
|
693
697
|
sized. Defaults to :py:obj:`None`.
|
|
694
698
|
alignments: List of alignments for each column
|
|
695
|
-
(ex.
|
|
696
|
-
or a single alignment to apply to all columns (ex.
|
|
699
|
+
(ex. [:attr:`Alignment.LEFT`, :attr:`Alignment.CENTER`, :attr:`Alignment.RIGHT`, :attr:`Alignment.DECIMAL`])
|
|
700
|
+
or a single alignment to apply to all columns (ex. :attr:`Alignment.LEFT`).
|
|
697
701
|
If not specified or set to :py:obj:`None`, all columns will be center-aligned.
|
|
698
702
|
Defaults to :py:obj:`None`.
|
|
699
703
|
|
|
@@ -9,10 +9,18 @@ table2ascii/exceptions.py
|
|
|
9
9
|
table2ascii/merge.py
|
|
10
10
|
table2ascii/options.py
|
|
11
11
|
table2ascii/preset_style.py
|
|
12
|
+
table2ascii/py.typed
|
|
12
13
|
table2ascii/table_style.py
|
|
13
14
|
table2ascii/table_to_ascii.py
|
|
14
15
|
table2ascii.egg-info/PKG-INFO
|
|
15
16
|
table2ascii.egg-info/SOURCES.txt
|
|
16
17
|
table2ascii.egg-info/dependency_links.txt
|
|
17
18
|
table2ascii.egg-info/requires.txt
|
|
18
|
-
table2ascii.egg-info/top_level.txt
|
|
19
|
+
table2ascii.egg-info/top_level.txt
|
|
20
|
+
tests/test_alignments.py
|
|
21
|
+
tests/test_cell_padding.py
|
|
22
|
+
tests/test_column_widths.py
|
|
23
|
+
tests/test_convert.py
|
|
24
|
+
tests/test_heading_cols.py
|
|
25
|
+
tests/test_merge.py
|
|
26
|
+
tests/test_styles.py
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from table2ascii import Alignment, PresetStyle, table2ascii as t2a
|
|
4
|
+
from table2ascii.exceptions import AlignmentCountMismatchError, InvalidAlignmentError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_first_left_four_right():
|
|
8
|
+
text = t2a(
|
|
9
|
+
header=["#", "G", "H", "R", "S"],
|
|
10
|
+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
|
|
11
|
+
footer=["SUM", "130", "140", "135", "130"],
|
|
12
|
+
first_col_heading=True,
|
|
13
|
+
alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4,
|
|
14
|
+
)
|
|
15
|
+
expected = (
|
|
16
|
+
"╔═════╦═══════════════════════╗\n"
|
|
17
|
+
"║ # ║ G H R S ║\n"
|
|
18
|
+
"╟─────╫───────────────────────╢\n"
|
|
19
|
+
"║ 1 ║ 30 40 35 30 ║\n"
|
|
20
|
+
"║ 2 ║ 30 40 35 30 ║\n"
|
|
21
|
+
"╟─────╫───────────────────────╢\n"
|
|
22
|
+
"║ SUM ║ 130 140 135 130 ║\n"
|
|
23
|
+
"╚═════╩═══════════════════════╝"
|
|
24
|
+
)
|
|
25
|
+
assert text == expected
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_wrong_number_of_alignments():
|
|
29
|
+
with pytest.raises(AlignmentCountMismatchError):
|
|
30
|
+
t2a(
|
|
31
|
+
header=["#", "G", "H", "R", "S"],
|
|
32
|
+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
|
|
33
|
+
footer=["SUM", "130", "140", "135", "130"],
|
|
34
|
+
first_col_heading=True,
|
|
35
|
+
alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_invalid_alignments():
|
|
40
|
+
with pytest.raises(InvalidAlignmentError):
|
|
41
|
+
t2a(
|
|
42
|
+
header=["#", "G", "H", "R", "S"],
|
|
43
|
+
body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]],
|
|
44
|
+
footer=["SUM", "130", "140", "135", "130"],
|
|
45
|
+
first_col_heading=True,
|
|
46
|
+
alignments=[9999, -1, Alignment.RIGHT, Alignment.CENTER, Alignment.RIGHT], # type: ignore
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_alignment_numeric_data():
|
|
51
|
+
text = t2a(
|
|
52
|
+
header=[1, "G", "H", "R", "S"],
|
|
53
|
+
body=[[1, 2, 3, 4, 5]],
|
|
54
|
+
footer=["A", "B", 1, 2, 3],
|
|
55
|
+
column_widths=[4, 5, 5, 4, 5],
|
|
56
|
+
alignments=[Alignment.RIGHT] + [Alignment.CENTER] * 4,
|
|
57
|
+
first_col_heading=True,
|
|
58
|
+
)
|
|
59
|
+
expected = (
|
|
60
|
+
"╔════╦══════════════════════╗\n"
|
|
61
|
+
"║ 1 ║ G H R S ║\n"
|
|
62
|
+
"╟────╫──────────────────────╢\n"
|
|
63
|
+
"║ 1 ║ 2 3 4 5 ║\n"
|
|
64
|
+
"╟────╫──────────────────────╢\n"
|
|
65
|
+
"║ A ║ B 1 2 3 ║\n"
|
|
66
|
+
"╚════╩══════════════════════╝"
|
|
67
|
+
)
|
|
68
|
+
assert text == expected
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_alignments_multiline_data():
|
|
72
|
+
text = t2a(
|
|
73
|
+
header=["Multiline\nHeader\nCell", "G", "Two\nLines", "R", "S"],
|
|
74
|
+
body=[[1, "Alpha\nBeta\nGamma", 3, 4, "One\nTwo"]],
|
|
75
|
+
footer=["A", "Footer\nBreak", 1, "Second\nCell\nBroken", 3],
|
|
76
|
+
alignments=[
|
|
77
|
+
Alignment.LEFT,
|
|
78
|
+
Alignment.RIGHT,
|
|
79
|
+
Alignment.CENTER,
|
|
80
|
+
Alignment.LEFT,
|
|
81
|
+
Alignment.CENTER,
|
|
82
|
+
],
|
|
83
|
+
)
|
|
84
|
+
expected = (
|
|
85
|
+
"╔═══════════════════════════════════════════╗\n"
|
|
86
|
+
"║ Multiline G Two R S ║\n"
|
|
87
|
+
"║ Header Lines ║\n"
|
|
88
|
+
"║ Cell ║\n"
|
|
89
|
+
"╟───────────────────────────────────────────╢\n"
|
|
90
|
+
"║ 1 Alpha 3 4 One ║\n"
|
|
91
|
+
"║ Beta Two ║\n"
|
|
92
|
+
"║ Gamma ║\n"
|
|
93
|
+
"╟───────────────────────────────────────────╢\n"
|
|
94
|
+
"║ A Footer 1 Second 3 ║\n"
|
|
95
|
+
"║ Break Cell ║\n"
|
|
96
|
+
"║ Broken ║\n"
|
|
97
|
+
"╚═══════════════════════════════════════════╝"
|
|
98
|
+
)
|
|
99
|
+
assert text == expected
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_decimal_alignment():
|
|
103
|
+
text = t2a(
|
|
104
|
+
header=["1.1.1", "G", "Long Header", "H", "R", "3.8"],
|
|
105
|
+
body=[[100.00001, 2, 3.14, 33, "AB", "1.5"], [10.0001, 22.0, 2.718, 3, "CD", "3.03"]],
|
|
106
|
+
footer=[10000.01, "123", 10.0, 0, "E", "A"],
|
|
107
|
+
alignments=[Alignment.DECIMAL] * 6,
|
|
108
|
+
first_col_heading=True,
|
|
109
|
+
style=PresetStyle.double_thin_box,
|
|
110
|
+
)
|
|
111
|
+
expected = (
|
|
112
|
+
"╔═════════════╦═══════╤═════════════╤════╤════╤═════════╗\n"
|
|
113
|
+
"║ 1.1.1 ║ G │ Long Header │ H │ R │ 3.8 ║\n"
|
|
114
|
+
"╠═════════════╬═══════╪═════════════╪════╪════╪═════════╣\n"
|
|
115
|
+
"║ 100.00001 ║ 2 │ 3.14 │ 33 │ AB │ 1.5 ║\n"
|
|
116
|
+
"╟─────────────╫───────┼─────────────┼────┼────┼─────────╢\n"
|
|
117
|
+
"║ 10.0001 ║ 22.0 │ 2.718 │ 3 │ CD │ 3.03 ║\n"
|
|
118
|
+
"╠═════════════╬═══════╪═════════════╪════╪════╪═════════╣\n"
|
|
119
|
+
"║ 10000.01 ║ 123 │ 10.0 │ 0 │ E │ A ║\n"
|
|
120
|
+
"╚═════════════╩═══════╧═════════════╧════╧════╧═════════╝"
|
|
121
|
+
)
|
|
122
|
+
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
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_number_alignments():
|
|
160
|
+
text = t2a(
|
|
161
|
+
header=["1.1.1", "G", "Long Header", "Another Long Header"],
|
|
162
|
+
body=[[100.00001, 2, 3.14, 6.28], [10.0001, 22.0, 2.718, 1.618]],
|
|
163
|
+
alignments=[Alignment.LEFT, Alignment.RIGHT, Alignment.CENTER, Alignment.RIGHT],
|
|
164
|
+
number_alignments=[Alignment.DECIMAL, Alignment.LEFT, Alignment.RIGHT, Alignment.DECIMAL],
|
|
165
|
+
)
|
|
166
|
+
expected = (
|
|
167
|
+
"╔══════════════════════════════════════════════════════╗\n"
|
|
168
|
+
"║ 1.1.1 G Long Header Another Long Header ║\n"
|
|
169
|
+
"╟──────────────────────────────────────────────────────╢\n"
|
|
170
|
+
"║ 100.00001 2 3.14 6.28 ║\n"
|
|
171
|
+
"║ 10.0001 22.0 2.718 1.618 ║\n"
|
|
172
|
+
"╚══════════════════════════════════════════════════════╝"
|
|
173
|
+
)
|
|
174
|
+
assert text == expected
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_single_number_alignments():
|
|
178
|
+
text = t2a(
|
|
179
|
+
header=["1.1.1", "G", "Long Header", "S"],
|
|
180
|
+
body=[[100.00001, 2, 3.14, 6.28], [10.0001, 22.0, 2.718, 1.618]],
|
|
181
|
+
alignments=[Alignment.LEFT, Alignment.CENTER, Alignment.CENTER, Alignment.RIGHT],
|
|
182
|
+
number_alignments=Alignment.RIGHT,
|
|
183
|
+
)
|
|
184
|
+
expected = (
|
|
185
|
+
"╔════════════════════════════════════════╗\n"
|
|
186
|
+
"║ 1.1.1 G Long Header S ║\n"
|
|
187
|
+
"╟────────────────────────────────────────╢\n"
|
|
188
|
+
"║ 100.00001 2 3.14 6.28 ║\n"
|
|
189
|
+
"║ 10.0001 22.0 2.718 1.618 ║\n"
|
|
190
|
+
"╚════════════════════════════════════════╝"
|
|
191
|
+
)
|
|
192
|
+
assert text == expected
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from table2ascii import Alignment, table2ascii as t2a
|
|
4
|
+
from table2ascii.exceptions import InvalidCellPaddingError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_without_cell_padding():
|
|
8
|
+
text = t2a(
|
|
9
|
+
header=["#", "G", "H", "R", "S"],
|
|
10
|
+
body=[[1, 2, 3, 4, 5]],
|
|
11
|
+
footer=["A", "B", 1, 2, 3],
|
|
12
|
+
first_col_heading=True,
|
|
13
|
+
cell_padding=0,
|
|
14
|
+
)
|
|
15
|
+
expected = (
|
|
16
|
+
"╔═╦═══════╗\n"
|
|
17
|
+
"║#║G H R S║\n"
|
|
18
|
+
"╟─╫───────╢\n"
|
|
19
|
+
"║1║2 3 4 5║\n"
|
|
20
|
+
"╟─╫───────╢\n"
|
|
21
|
+
"║A║B 1 2 3║\n"
|
|
22
|
+
"╚═╩═══════╝"
|
|
23
|
+
)
|
|
24
|
+
assert text == expected
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_column_width_and_alignment_without_cell_padding():
|
|
28
|
+
text = t2a(
|
|
29
|
+
header=["#", "G", "H", "R", "S"],
|
|
30
|
+
body=[[1, 2, 3, 4, 5]],
|
|
31
|
+
footer=["A", "B", 1, 2, 3],
|
|
32
|
+
column_widths=[4, 8, 5, 4, 5],
|
|
33
|
+
alignments=[
|
|
34
|
+
Alignment.LEFT,
|
|
35
|
+
Alignment.CENTER,
|
|
36
|
+
Alignment.RIGHT,
|
|
37
|
+
Alignment.LEFT,
|
|
38
|
+
Alignment.RIGHT,
|
|
39
|
+
],
|
|
40
|
+
first_col_heading=True,
|
|
41
|
+
cell_padding=0,
|
|
42
|
+
)
|
|
43
|
+
expected = (
|
|
44
|
+
"╔════╦═════════════════════════╗\n"
|
|
45
|
+
"║# ║ G H R S║\n"
|
|
46
|
+
"╟────╫─────────────────────────╢\n"
|
|
47
|
+
"║1 ║ 2 3 4 5║\n"
|
|
48
|
+
"╟────╫─────────────────────────╢\n"
|
|
49
|
+
"║A ║ B 1 2 3║\n"
|
|
50
|
+
"╚════╩═════════════════════════╝"
|
|
51
|
+
)
|
|
52
|
+
assert text == expected
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_cell_padding_more_than_one():
|
|
56
|
+
text = t2a(
|
|
57
|
+
header=["#", "G", "H", "R", "S"],
|
|
58
|
+
body=[[1, 2, 3, 4, 5]],
|
|
59
|
+
footer=["A", "B", 1, 2, 3],
|
|
60
|
+
first_col_heading=True,
|
|
61
|
+
cell_padding=2,
|
|
62
|
+
)
|
|
63
|
+
expected = (
|
|
64
|
+
"╔═════╦═══════════════════════╗\n"
|
|
65
|
+
"║ # ║ G H R S ║\n"
|
|
66
|
+
"╟─────╫───────────────────────╢\n"
|
|
67
|
+
"║ 1 ║ 2 3 4 5 ║\n"
|
|
68
|
+
"╟─────╫───────────────────────╢\n"
|
|
69
|
+
"║ A ║ B 1 2 3 ║\n"
|
|
70
|
+
"╚═════╩═══════════════════════╝"
|
|
71
|
+
)
|
|
72
|
+
assert text == expected
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_negative_cell_padding():
|
|
76
|
+
with pytest.raises(InvalidCellPaddingError):
|
|
77
|
+
t2a(
|
|
78
|
+
header=["#", "G", "H", "R", "S"],
|
|
79
|
+
body=[[1, 2, 3, 4, 5]],
|
|
80
|
+
footer=["A", "B", 1, 2, 3],
|
|
81
|
+
first_col_heading=True,
|
|
82
|
+
cell_padding=-1,
|
|
83
|
+
)
|