diff --git a/README.md b/README.md
index e6ec014..d64cc09 100644
--- a/README.md
+++ b/README.md
@@ -149,33 +149,35 @@ See a list of all preset styles [here](https://table2ascii.readthedocs.io/en/lat
All parameters are optional.
-| Option | Type | Default | Description |
-| :-----------------: | :---------------: | :----------: | :----------------------------------------------------------------------------------------: |
-| `header` | `List[str]` | `None` | First row of table seperated by header row seperator |
-| `body` | `List[List[str]]` | `None` | List of rows for the main section of the table |
-| `footer` | `List[str]` | `None` | Last row of table seperated by header row seperator |
-| `column_widths` | `List[int]` | automatic | List of column widths in characters for each column |
-| `alignments` | `List[int]` | all centered | Alignments for each column
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) |
-| `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 |
+| Option | Type | Default | Description |
+| :-----------------: | :-------------------: | :-------------------: | :-------------------------------------------------------------------------------: |
+| `header` | `List[Any]` | `None` | First table row seperated by header row seperator. Values should support `str()`. |
+| `body` | `List[List[Any]]` | `None` | List of rows for the main section of the table. Values should support `str()`. |
+| `footer` | `List[Any]` | `None` | Last table row seperated by header row seperator. Values should support `str()`. |
+| `column_widths` | `List[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column |
+| `alignments` | `List[Alignment]` | `None` (all centered) | Column alignments
(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) |
+| `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 |
+
+See the [API Reference](https://table2ascii.readthedocs.io/en/latest/api.html) for more info.
## 👨🎨 Use cases
### Discord messages and embeds
-* Display tables nicely inside markdown codeblocks on Discord
-* Useful for making Discord bots with [Discord.py](https://github.com/Rapptz/discord.py)
+- Display tables nicely inside markdown code blocks on Discord
+- Useful for making Discord bots with [Discord.py](https://github.com/Rapptz/discord.py)

### Terminal outputs
-* Tables display nicely whenever monospace fonts are fully supported
-* Tables make terminal outputs look more professional
+- Tables display nicely whenever monospace fonts are fully supported
+- Tables make terminal outputs look more professional

-
## 🤗 Contributing
Contributions are welcome!
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e1631f0
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+typing_extensions>=4.0.0,<5
\ No newline at end of file
diff --git a/table2ascii/annotations.py b/table2ascii/annotations.py
new file mode 100644
index 0000000..cb78bdd
--- /dev/null
+++ b/table2ascii/annotations.py
@@ -0,0 +1,23 @@
+from abc import abstractmethod
+from typing import TYPE_CHECKING
+
+try:
+ # Python 3.8+
+ from typing import Protocol, runtime_checkable
+except ImportError:
+ # Python 3.7
+ from typing_extensions import Protocol, runtime_checkable
+
+if TYPE_CHECKING:
+ from typing import Protocol
+
+
+@runtime_checkable
+class SupportsStr(Protocol):
+ """An ABC with one abstract method __str__."""
+
+ __slots__ = ()
+
+ @abstractmethod
+ def __str__(self) -> str:
+ pass
diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py
index b0f07bf..7b8ab81 100644
--- a/table2ascii/table_to_ascii.py
+++ b/table2ascii/table_to_ascii.py
@@ -1,7 +1,8 @@
from math import ceil, floor
-from typing import Any, Callable, List, Optional, Union
+from typing import Callable, List, Optional, Union
from .alignment import Alignment
+from .annotations import SupportsStr
from .options import Options
from .preset_style import PresetStyle
from .table_style import TableStyle
@@ -12,9 +13,9 @@ class TableToAscii:
def __init__(
self,
- header: Optional[List[Any]],
- body: Optional[List[List[Any]]],
- footer: Optional[List[Any]],
+ header: Optional[List[SupportsStr]],
+ body: Optional[List[List[SupportsStr]]],
+ footer: Optional[List[SupportsStr]],
options: Options,
):
"""
@@ -103,7 +104,9 @@ def widest_line(text: str) -> int:
# get the width necessary for each column
for i in range(self.__columns):
# col_widest returns the width of the widest line in the ith cell of a given list
- col_widest: Callable[[List[Any], int], int] = lambda row, i=i: widest_line(str(row[i]))
+ col_widest: Callable[[List[SupportsStr], int], int] = lambda row, i=i: widest_line(
+ str(row[i])
+ )
# number of characters in column of i of header, each body row, and footer
header_size = col_widest(self.__header) if self.__header else 0
body_size = map(col_widest, self.__body) if self.__body else [0]
@@ -112,7 +115,7 @@ def widest_line(text: str) -> int:
column_widths.append(max(header_size, *body_size, footer_size) + 2)
return column_widths
- def __pad(self, cell_value: Any, width: int, alignment: Alignment) -> str:
+ def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> str:
"""
Pad a string of text to a given width with specified alignment
@@ -258,7 +261,7 @@ def __heading_sep_to_ascii(self) -> str:
filler=self.__style.heading_row_sep,
)
- def __body_to_ascii(self, body: List[List[Any]]) -> str:
+ def __body_to_ascii(self, body: List[List[SupportsStr]]) -> str:
"""
Assembles the body of the ascii table
@@ -310,9 +313,9 @@ def to_ascii(self) -> str:
def table2ascii(
- header: Optional[List[Any]] = None,
- body: Optional[List[List[Any]]] = None,
- footer: Optional[List[Any]] = None,
+ header: Optional[List[SupportsStr]] = None,
+ body: Optional[List[List[SupportsStr]]] = None,
+ footer: Optional[List[SupportsStr]] = None,
*,
first_col_heading: bool = False,
last_col_heading: bool = False,
@@ -324,12 +327,12 @@ def table2ascii(
Convert a 2D Python table to ASCII text
Args:
- header: List of column values in the table's header row. If not specified,
- the table will not have a header row.
- body: 2-dimensional list of values in the table's body. If not specified,
- the table will not have a body.
- footer: List of column values in the table's footer row. If not specified,
- the table will not have a footer row.
+ 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.
+ body: 2-dimensional list of values in the table's body. All values should be :class:`str`
+ or support :class:`str` conversion. If not specified, the table will not have a body.
+ footer: List of column values in the table's footer row. All values should be :class:`str`
+ or support :class:`str` conversion. If not specified, the table will not have a footer row.
first_col_heading: Whether to add a header column separator after the first column.
Defaults to :py:obj:`False`.
last_col_heading: Whether to add a header column separator before the last column.
diff --git a/tests/test_convert.py b/tests/test_convert.py
index 5c0653a..3e7a045 100644
--- a/tests/test_convert.py
+++ b/tests/test_convert.py
@@ -201,6 +201,29 @@ def test_numeric_data():
assert text == expected
+def test_stringifiable_classes():
+ class Foo:
+ def __str__(self):
+ return "Foo"
+
+ text = t2a(
+ header=[1, Foo(), None],
+ body=[[1, Foo(), None]],
+ footer=[1, Foo(), None],
+ first_col_heading=True,
+ )
+ expected = (
+ "╔═══╦════════════╗\n"
+ "║ 1 ║ Foo None ║\n"
+ "╟───╫────────────╢\n"
+ "║ 1 ║ Foo None ║\n"
+ "╟───╫────────────╢\n"
+ "║ 1 ║ Foo None ║\n"
+ "╚═══╩════════════╝"
+ )
+ assert text == expected
+
+
def test_multiline_cells():
text = t2a(
header=["Multiline\nHeader\nCell", "G", "Two\nLines", "R", "S"],