diff --git a/README.md b/README.md index 66c23e9..5689715 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,40 @@ print(output) """ ``` +### ๐Ÿช„ Merge adjacent cells + +```py +from table2ascii import table2ascii, Merge, PresetStyle + +output = table2ascii( + header=["#", "G", "Merge", Merge.LEFT, "S"], + body=[ + [1, 5, 6, 200, Merge.LEFT], + [2, "E", "Long cell", Merge.LEFT, Merge.LEFT], + ["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"], + ], + footer=["SUM", "100", "200", Merge.LEFT, "300"], + style=PresetStyle.double_thin_box, + first_col_heading=True, +) + +print(output) + +""" +โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•— +โ•‘ # โ•‘ G โ”‚ Merge โ”‚ S โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•ฃ +โ•‘ 1 โ•‘ 5 โ”‚ 6 โ”‚ 200 โ•‘ +โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข +โ•‘ 2 โ•‘ E โ”‚ Long cell โ•‘ +โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•จโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ•ข +โ•‘ Bonus โ”‚ F โ”‚ G โ•‘ +โ• โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ +โ•‘ SUM โ•‘ 100 โ”‚ 200 โ”‚ 300 โ•‘ +โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ• +""" +``` + ## โš™๏ธ Options All parameters are optional. diff --git a/docs/source/api.rst b/docs/source/api.rst index b890a84..6b16e63 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -8,13 +8,20 @@ table2ascii .. autofunction:: table2ascii - Alignment ~~~~~~~~~ .. autoenum:: Alignment :members: +.. _Merge: + +Merge +~~~~~ + +.. autoenum:: Merge + :members: + PresetStyle ~~~~~~~~~~~ @@ -51,3 +58,10 @@ Exceptions .. autoexception:: table2ascii.exceptions.ColumnWidthTooSmallError .. autoexception:: table2ascii.exceptions.InvalidAlignmentError + +.. autoexception:: table2ascii.exceptions.TableStyleTooLongError + +Warnings +~~~~~~~~ + +.. autoclass:: table2ascii.exceptions.TableStyleTooShortWarning diff --git a/docs/source/styles.rst b/docs/source/styles.rst index 85f3bf1..abb9ddf 100644 --- a/docs/source/styles.rst +++ b/docs/source/styles.rst @@ -286,6 +286,29 @@ Preset styles โ•‘ 2 โ•‘ 30 40 35 30 โ•‘ โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +.. _PresetStyle.double_thin_box: + +`double_thin_box` +~~~~~~~~~~~~~~~~~ + +.. code-block:: none + + โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•— + โ•‘ # โ•‘ G โ”‚ H โ”‚ R โ”‚ S โ•‘ + โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ + โ•‘ 1 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ 2 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ + โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ + โ•‘ SUM โ•‘ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ•‘ + โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ• + + โ•”โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•— + โ•‘ 1 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ + โ•Ÿโ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ•ข + โ•‘ 2 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ + โ•šโ•โ•โ•โ•ฉโ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•งโ•โ•โ•โ•โ• + .. _PresetStyle.double_thin_compact: `double_thin_compact` diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 9b43dbd..70f8487 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -59,8 +59,8 @@ Set column widths and alignments header=["#", "G", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], first_col_heading=True, - column_widths=[5] * 5, # [5, 5, 5, 5, 5] - alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4, # First is left, remaining 4 are right + column_widths=[5, 5, 5, 5, 5], + alignments=[Alignment.LEFT] + [Alignment.RIGHT] * 4, ) print(output) @@ -84,7 +84,7 @@ Use a preset style output = table2ascii( header=["First", "Second", "Third", "Fourth"], body=[["10", "30", "40", "35"], ["20", "10", "20", "5"]], - column_widths=[10] * 4, + column_widths=[10, 10, 10, 10], style=PresetStyle.ascii_box ) @@ -130,7 +130,7 @@ Check :ref:`TableStyle` for more info. output = table2ascii( header=["First", "Second", "Third"], body=[["10", "30", "40"], ["20", "10", "20"], ["30", "20", "30"]], - style=my_style + style=my_style, ) print(output) @@ -143,4 +143,41 @@ Check :ref:`TableStyle` for more info. | 20 : 10 : 20 | | 30 : 20 : 30 | *-------'--------'-------* - """ \ No newline at end of file + """ + +Merge adjacent cells +~~~~~~~~~~~~~~~~~~~~ + +Check :ref:`Merge` for more info. + +.. code:: py + + from table2ascii import table2ascii, Merge, PresetStyle + + output = table2ascii( + header=["#", "G", "Merge", Merge.LEFT, "S"], + body=[ + [1, 5, 6, 200, Merge.LEFT], + [2, "E", "Long cell", Merge.LEFT, Merge.LEFT], + ["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"], + ], + footer=["SUM", "100", "200", Merge.LEFT, "300"], + style=PresetStyle.double_thin_box, + first_col_heading=True, + ) + + print(output) + + """ + โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•— + โ•‘ # โ•‘ G โ”‚ Merge โ”‚ S โ•‘ + โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•ฃ + โ•‘ 1 โ•‘ 5 โ”‚ 6 โ”‚ 200 โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ 2 โ•‘ E โ”‚ Long cell โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•จโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ Bonus โ”‚ F โ”‚ G โ•‘ + โ• โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ + โ•‘ SUM โ•‘ 100 โ”‚ 200 โ”‚ 300 โ•‘ + โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ• + """ diff --git a/table2ascii/__init__.py b/table2ascii/__init__.py index 22854b3..aade353 100644 --- a/table2ascii/__init__.py +++ b/table2ascii/__init__.py @@ -3,6 +3,7 @@ """ from .alignment import Alignment +from .merge import Merge from .preset_style import PresetStyle from .table_style import TableStyle from .table_to_ascii import table2ascii @@ -10,8 +11,9 @@ __version__ = "1.0.0" __all__ = [ - "table2ascii", "Alignment", - "TableStyle", + "Merge", "PresetStyle", + "TableStyle", + "table2ascii", ] diff --git a/table2ascii/exceptions.py b/table2ascii/exceptions.py index efdf177..4af1a26 100644 --- a/table2ascii/exceptions.py +++ b/table2ascii/exceptions.py @@ -189,3 +189,54 @@ def _message(self): f"Invalid alignment: {self.alignment!r} is not a valid alignment. " f"Valid alignments are: {', '.join(a.__repr__() for a in Alignment)}" ) + + +class TableStyleTooLongError(Table2AsciiError, ValueError): + """Exception raised when the number of characters passed in the string + for creating the table style exceeds the number of parameters that the + table style accepts + + This class is a subclass of :class:`Table2AsciiError` and :class:`ValueError`. + + Attributes: + string (str): The string that caused the error + max_characters (int): The maximum number of characters that are allowed + """ + + def __init__(self, string: str, max_characters: int): + self.string = string + self.max_characters = max_characters + super().__init__(self._message()) + + def _message(self): + return ( + f"Too many characters for table style: {len(self.string)} characters " + f"found, but the maximum number of characters allowed is {self.max_characters}." + ) + + +class TableStyleTooShortWarning(UserWarning): + """Warning raised when the number of characters passed in the string + for creating the table style is fewer than the number of parameters + that the table style accepts + + This class is a subclass of :class:`UserWarning`. + + It can be silenced using :func:`warnings.filterwarnings`. + + Attributes: + string (str): The string that caused the warning + max_characters (int): The number of characters that :class:`TableStyle` accepts + """ + + def __init__(self, string: str, max_characters: int): + self.string = string + self.max_characters = max_characters + super().__init__(self._message()) + + def _message(self): + return ( + f"Too few characters for table style: {len(self.string)} characters " + f"found, but table styles can accept {self.max_characters} characters. " + f"Missing characters will be replaced with spaces." + ) diff --git a/table2ascii/merge.py b/table2ascii/merge.py new file mode 100644 index 0000000..3e8888b --- /dev/null +++ b/table2ascii/merge.py @@ -0,0 +1,43 @@ +from enum import Enum + + +class Merge(Enum): + """Enum for merging table cells + + Using :attr:`Merge.LEFT` in a table cell will merge the cell it is used in + with the cell to its left. + + In the case that the contents of the merged cell are longer than the + combined widths of the unmerged cells in the rows above and below, + the merged cell will be wrapped onto multiple lines. The ``column_widths`` + option can be used to control the widths of the unmerged cells. + + Example:: + + from table2ascii import table2ascii, Merge, PresetStyle + + table2ascii( + header=["Name", "Price", "Category", "Stock"], + body=[["Milk", "$2.99", "N/A", Merge.LEFT]], + footer=["Description", "Milk is a nutritious beverage", Merge.LEFT, Merge.LEFT], + style=PresetStyle.double_box, + ) + + \"\"\" + โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•— + โ•‘ Name โ•‘ Price โ•‘ Category โ•‘ Stock โ•‘ + โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•ฃ + โ•‘ Milk โ•‘ $2.99 โ•‘ N/A โ•‘ + โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ + โ•‘ Description โ•‘ Milk is a nutritious โ•‘ + โ•‘ โ•‘ beverage โ•‘ + โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + \"\"\" + + .. versionadded:: 1.0.0 + """ + + LEFT = 0 + + def __str__(self): + return "" diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py index 486ea1c..d82a0a9 100644 --- a/table2ascii/preset_style.py +++ b/table2ascii/preset_style.py @@ -16,33 +16,34 @@ class PresetStyle: ) """ - thin = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”คโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜") - thin_box = TableStyle.from_string("โ”Œโ”€โ”ฌโ”ฌโ”โ”‚โ”‚โ”‚โ”œโ”€โ”ผโ”ผโ”คโ”œโ”€โ”ผโ”ผโ”คโ””โ”ดโ”ดโ”˜") - thin_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”คโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏ") - thin_compact = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”ค โ””โ”ดโ”€โ”˜") - thin_compact_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”ค โ•ฐโ”ดโ”€โ•ฏ") - thin_thick = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”โ”โ”ฟโ”โ”ฅโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜") - thin_thick_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”โ”โ”ฟโ”โ”ฅโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏ") - thin_double = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ•žโ•โ•ชโ•โ•กโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜") - thin_double_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ•žโ•โ•ชโ•โ•กโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏ") - thick = TableStyle.from_string("โ”โ”โ”ณโ”โ”“โ”ƒโ”ƒ โ”ฃโ”โ•‹โ”โ”ซโ”ฃโ”โ•‹โ”โ”ซโ”—โ”ปโ”โ”›") - thick_box = TableStyle.from_string("โ”โ”โ”ณโ”ณโ”“โ”ƒโ”ƒโ”ƒโ”ฃโ”โ•‹โ•‹โ”ซโ”ฃโ”โ•‹โ•‹โ”ซโ”—โ”ปโ”ปโ”›") - thick_compact = TableStyle.from_string("โ”โ”โ”ณโ”โ”“โ”ƒโ”ƒ โ”ฃโ”โ•‹โ”โ”ซ โ”—โ”ปโ”โ”›") - double = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ• โ•โ•ฌโ•โ•ฃโ• โ•โ•ฌโ•โ•ฃโ•šโ•ฉโ•โ•") - double_box = TableStyle.from_string("โ•”โ•โ•ฆโ•ฆโ•—โ•‘โ•‘โ•‘โ• โ•โ•ฌโ•ฌโ•ฃโ• โ•โ•ฌโ•ฌโ•ฃโ•šโ•ฉโ•ฉโ•") - double_compact = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ• โ•โ•ฌโ•โ•ฃ โ•šโ•ฉโ•โ•") - double_thin_compact = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ•Ÿโ”€โ•ซโ”€โ•ข โ•šโ•ฉโ•โ•") - minimalist = TableStyle.from_string(" โ”€โ”€โ”€ โ”‚ โ”โ”โ” โ”€โ”€โ”€ โ”€โ”€ ") - borderless = TableStyle.from_string(" โ”ƒ โ” ") - simple = TableStyle.from_string(" โ• โ•‘ โ• ") - ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+") - ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++") - ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+") - ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+") - ascii_minimalist = TableStyle.from_string(" --- | === --- -- ") - ascii_borderless = TableStyle.from_string(" | - ") - ascii_simple = TableStyle.from_string(" = | = ") - ascii_rounded = TableStyle.from_string(r"/===\|| |=|=||-|-|\|=/") - ascii_rounded_box = TableStyle.from_string(r"/===\||||=||||-|||\||/") - markdown = TableStyle.from_string(" ||||-||| ") - plain = TableStyle.from_string(" ").set(left_and_right_edge="") + thin = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”คโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜โ”€โ”€โ”€โ”€โ”ฌโ”ดโ”ฌโ”ด") + thin_box = TableStyle.from_string("โ”Œโ”€โ”ฌโ”ฌโ”โ”‚โ”‚โ”‚โ”œโ”€โ”ผโ”ผโ”คโ”œโ”€โ”ผโ”ผโ”คโ””โ”ดโ”ดโ”˜โ”ฌโ”ดโ”ฌโ”ดโ”ฌโ”ดโ”ฌโ”ด") + thin_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”คโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏโ”€โ”€โ”€โ”€โ”ฌโ”ดโ”ฌโ”ด") + thin_compact = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”ค โ””โ”ดโ”€โ”˜ โ”€โ”€ โ”ฌโ”ด") + thin_compact_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”œโ”€โ”ผโ”€โ”ค โ•ฐโ”ดโ”€โ•ฏ โ”€โ”€ โ”ฌโ”ด") + thin_thick = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ”โ”โ”ฟโ”โ”ฅโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜โ”€โ”€โ”โ”โ”ฌโ”ดโ”ฏโ”ท") + thin_thick_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ”โ”โ”ฟโ”โ”ฅโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏโ”€โ”€โ”โ”โ”ฌโ”ดโ”ฏโ”ท") + thin_double = TableStyle.from_string("โ”Œโ”€โ”ฌโ”€โ”โ”‚โ”‚ โ•žโ•โ•ชโ•โ•กโ”œโ”€โ”ผโ”€โ”คโ””โ”ดโ”€โ”˜โ”€โ”€โ•โ•โ”ฌโ”ดโ•คโ•ง") + thin_double_rounded = TableStyle.from_string("โ•ญโ”€โ”ฌโ”€โ•ฎโ”‚โ”‚ โ•žโ•โ•ชโ•โ•กโ”œโ”€โ”ผโ”€โ”คโ•ฐโ”ดโ”€โ•ฏโ”€โ”€โ•โ•โ”ฌโ”ดโ•คโ•ง") + thick = TableStyle.from_string("โ”โ”โ”ณโ”โ”“โ”ƒโ”ƒ โ”ฃโ”โ•‹โ”โ”ซโ”ฃโ”โ•‹โ”โ”ซโ”—โ”ปโ”โ”›โ”โ”โ”โ”โ”ณโ”ปโ”ณโ”ป") + thick_box = TableStyle.from_string("โ”โ”โ”ณโ”ณโ”“โ”ƒโ”ƒโ”ƒโ”ฃโ”โ•‹โ•‹โ”ซโ”ฃโ”โ•‹โ•‹โ”ซโ”—โ”ปโ”ปโ”›โ”ณโ”ปโ”ณโ”ปโ”ณโ”ปโ”ณโ”ป") + thick_compact = TableStyle.from_string("โ”โ”โ”ณโ”โ”“โ”ƒโ”ƒ โ”ฃโ”โ•‹โ”โ”ซ โ”—โ”ปโ”โ”› โ”โ” โ”ณโ”ป") + double = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ• โ•โ•ฌโ•โ•ฃโ• โ•โ•ฌโ•โ•ฃโ•šโ•ฉโ•โ•โ•โ•โ•โ•โ•ฆโ•ฉโ•ฆโ•ฉ") + double_box = TableStyle.from_string("โ•”โ•โ•ฆโ•ฆโ•—โ•‘โ•‘โ•‘โ• โ•โ•ฌโ•ฌโ•ฃโ• โ•โ•ฌโ•ฌโ•ฃโ•šโ•ฉโ•ฉโ•โ•ฆโ•ฉโ•ฆโ•ฉโ•ฆโ•ฉโ•ฆโ•ฉ") + double_compact = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ• โ•โ•ฌโ•โ•ฃ โ•šโ•ฉโ•โ• โ•โ• โ•ฆโ•ฉ") + double_thin_box = TableStyle.from_string("โ•”โ•โ•ฆโ•คโ•—โ•‘โ•‘โ”‚โ• โ•โ•ฌโ•ชโ•ฃโ•Ÿโ”€โ•ซโ”ผโ•ขโ•šโ•ฉโ•งโ•โ”ฌโ”ดโ•คโ•งโ•ฅโ•จโ•ฆโ•ฉ") + double_thin_compact = TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ•Ÿโ”€โ•ซโ”€โ•ข โ•šโ•ฉโ•โ• โ”€โ”€ โ•ฅโ•จ") + minimalist = TableStyle.from_string(" โ”€โ”€โ”€ โ”‚ โ”โ”โ” โ”€โ”€โ”€ โ”€โ”€ โ”€โ”€โ”โ”โ”ฌโ”ดโ”ฏโ”ท") + borderless = TableStyle.from_string(" โ”ƒ โ” โ”โ” โ”โ”") + simple = TableStyle.from_string(" โ• โ•‘ โ• โ•โ• โ•ฆโ•ฉ") + ascii = TableStyle.from_string("+-+-+|| +-+-++-+-+++-+----++++") + ascii_box = TableStyle.from_string("+-+++|||+-++++-+++++++++++++++") + ascii_compact = TableStyle.from_string("+-+-+|| +-+-+ ++-+ -- --") + ascii_double = TableStyle.from_string("+-+-+|| +=+=++-+-+++-+--==--==") + ascii_minimalist = TableStyle.from_string(" --- | === --- -- --==--==") + ascii_borderless = TableStyle.from_string(" | - -- --") + ascii_simple = TableStyle.from_string(" = | = == ==") + ascii_rounded = TableStyle.from_string(r"/===\|| |=|=||-|-|\|=/--==--==") + ascii_rounded_box = TableStyle.from_string(r"/===\||||=||||-|||\||/--==--==") + markdown = TableStyle.from_string(" ||||-||| -- --") + plain = TableStyle.from_string(" " * 30).set(left_and_right_edge="") diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 53cc4b5..620eb77 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -1,11 +1,14 @@ from dataclasses import dataclass +import warnings + +from .exceptions import TableStyleTooShortWarning, TableStyleTooLongError @dataclass class TableStyle: """Class for storing information about a table style - Parts of the table labeled alphabetically: + Parts of the table labeled alphabetically from A to V: .. code-block:: @@ -19,12 +22,12 @@ class TableStyle: F G H H H F SBBBBBTBBBBBUBBBBBUBBBBBUBBBBBV - How the theme is displayed with double thickness for - heading rows and columns and thin for normal rows and columns: + How the theme is displayed using :ref:`PresetStyle.double_thin_box` + as an example: .. code-block:: - โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•— + โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•— โ•‘ # โ•‘ G โ”‚ H โ”‚ R โ”‚ S โ•‘ โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ โ•‘ 1 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ @@ -32,7 +35,36 @@ class TableStyle: โ•‘ 2 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘ โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ โ•‘ SUM โ•‘ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ•‘ - โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ• + โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ• + + In addition to the parts above, W-Z and the four fields that follow + (labeled 0-3) are used for top and bottom edges of merged cells as shown: + + .. code-block:: + + โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•— + โ•‘ Header โ”‚ โ”‚ โ•‘ + โ• โ•โ•โ•โ•โ•โ•[2]โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•[Z]โ•โ•โ•โ•โ•โ•ฃ + โ•‘ โ•‘ โ”‚ โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”€[1]โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ โ”‚ โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”€[0]โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€[W]โ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ โ•‘ โ”‚ โ”‚ โ•‘ + โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€[X]โ”€โ”€โ”€โ”€โ”€โ•ข + โ•‘ โ•‘ โ”‚ โ•‘ + โ• โ•โ•โ•โ•โ•โ•[3]โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•[Y]โ•โ•โ•โ•โ•โ•ฃ + โ•‘ Footer โ”‚ โ”‚ โ•‘ + โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ• + + [W] = โ”ฌ [X] = โ”ด [Y] = โ•ค [Z] = โ•ง + [0] = โ•ฅ [1] = โ•จ [2] = โ•ฆ [3] = โ•ฉ + + .. versionchanged:: 1.0.0 + + Added fields for edges of merged cells -- ``col_row_top_tee``, ``col_row_bottom_tee``, + ``heading_row_top_tee``, ``heading_row_bottom_tee``, ``heading_col_body_row_top_tee``, + ``heading_col_body_row_bottom_tee``, ``heading_col_heading_row_top_tee``, + ``heading_col_heading_row_bottom_tee`` """ # parts of the table @@ -58,11 +90,25 @@ class TableStyle: heading_col_bottom_tee: str # T bottom_tee: str # U bottom_right_corner: str # V + col_row_top_tee: str # W + col_row_bottom_tee: str # X + heading_row_top_tee: str # Y + heading_row_bottom_tee: str # Z + heading_col_body_row_top_tee: str # 0 + heading_col_body_row_bottom_tee: str # 1 + heading_col_heading_row_top_tee: str # 2 + heading_col_heading_row_bottom_tee: str # 3 @classmethod def from_string(cls, string: str) -> "TableStyle": """Create a TableStyle from a string + .. versionchanged:: 1.0.0 + + The string will be padded on the right with spaces if it is too short + and :class:`~table2ascii.exceptions.TableStyleTooLongError` will be + raised if it is too long. + Args: string: The string to create the TableStyle from @@ -71,8 +117,19 @@ def from_string(cls, string: str) -> "TableStyle": Example:: - TableStyle.from_string("โ•”โ•โ•ฆโ•โ•—โ•‘โ•‘ โ•Ÿโ”€โ•ซโ”€โ•ข โ•šโ•ฉโ•โ•") + TableStyle.from_string("โ•”โ•โ•ฆโ•คโ•—โ•‘โ•‘โ”‚โ• โ•โ•ฌโ•ชโ•ฃโ•Ÿโ”€โ•ซโ”ผโ•ขโ•šโ•ฉโ•งโ•โ”ฌโ”ดโ•คโ•งโ•ฅโ•จโ•ฆโ•ฉ") + + Raises: + TableStyleTooLongError: If the string is too long """ + num_params = len(cls.__dataclass_fields__) + # if the string is too long, raise an error + if len(string) > num_params: + raise TableStyleTooLongError(string, num_params) + # if the string is too short, show a warning and pad it with spaces + elif len(string) < num_params: + string += " " * (num_params - len(string)) + warnings.warn(TableStyleTooShortWarning(string, num_params), stacklevel=2) return cls(*string) def set(self, **kwargs) -> "TableStyle": diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index fcf8c51..835db3d 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -1,5 +1,6 @@ from __future__ import annotations +import textwrap from math import ceil, floor from wcwidth import wcswidth @@ -15,6 +16,7 @@ InvalidAlignmentError, InvalidCellPaddingError, ) +from .merge import Merge from .options import Options from .preset_style import PresetStyle from .table_style import TableStyle @@ -71,6 +73,9 @@ def __init__( if self.__cell_padding < 0: raise InvalidCellPaddingError(self.__cell_padding) + # if any row starts with Merge.LEFT, replace it with an empty string + self.__fix_rows_beginning_with_merge() + def __count_columns(self) -> int: """Get the number of columns in the table based on the provided header, footer, and body lists. @@ -98,13 +103,21 @@ def widest_line(value: SupportsStr) -> int: text = str(value) return max(self.__str_width(line) for line in text.splitlines()) if len(text) else 0 + def get_column_width(row: list[SupportsStr], column: int) -> int: + """Get the width of a cell in a column""" + value = row[column] + next_value = row[column + 1] if column < self.__columns - 1 else None + if value is Merge.LEFT or next_value is Merge.LEFT: + return 0 + return widest_line(value) + column_widths = [] # get the width necessary for each column for i in range(self.__columns): # number of characters in column of i of header, each body row, and footer - header_size = widest_line(self.__header[i]) if self.__header else 0 - body_size = max(widest_line(row[i]) for row in self.__body) if self.__body else 0 - footer_size = widest_line(self.__footer[i]) if self.__footer else 0 + header_size = get_column_width(self.__header, i) if self.__header else 0 + body_size = max(get_column_width(row, i) for row in self.__body) if self.__body else 0 + footer_size = get_column_width(self.__footer, i) if self.__footer else 0 # get the max and add 2 for padding each side with a space depending on cell padding column_widths.append(max(header_size, body_size, footer_size) + self.__cell_padding * 2) return column_widths @@ -134,6 +147,17 @@ def __calculate_column_widths(self, user_column_widths: list[int | None] | None) column_widths[i] = option return column_widths + def __fix_rows_beginning_with_merge(self) -> None: + """Fix rows that begin with Merge.LEFT by replacing the cell with an empty string.""" + if self.__body: + for row_index, row in enumerate(self.__body): + if row and row[0] == Merge.LEFT: + self.__body[row_index][0] = "" + if self.__header and self.__header[0] == Merge.LEFT: + self.__header[0] = "" + if self.__footer and self.__footer[0] == Merge.LEFT: + self.__footer[0] = "" + def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> str: """Pad a string of text to a given width with specified alignment @@ -162,6 +186,33 @@ def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> st return (" " * (width - text_width)) + padded_text raise InvalidAlignmentError(alignment) + def __wrap_long_lines_in_merged_cells( + self, row: list[SupportsStr], column_separator: str + ) -> list[SupportsStr]: + """Wrap long lines in merged cells to the width of the merged cell + + Args: + row: The row to wrap cells in + column_separator: The column separator between cells + + Returns: + The row with long lines wrapped + """ + wrapped_row: list[SupportsStr] = [] + for col_index, cell in enumerate(row): + if cell is Merge.LEFT: + wrapped_row.append(cell) + continue + merged_width = self.__column_widths[col_index] + # if the columns to the right are Merge.LEFT, add their width to the padding + for other_col_index in range(col_index + 1, self.__columns): + if row[other_col_index] is not Merge.LEFT: + break + merged_width += self.__column_widths[other_col_index] + len(column_separator) + cell = textwrap.fill(str(cell), merged_width - self.__cell_padding * 2) + wrapped_row.append(cell) + return wrapped_row + def __row_to_ascii( self, left_edge: str, @@ -169,6 +220,12 @@ def __row_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, + top_tee: str | None = None, + bottom_tee: str | None = None, + heading_col_top_tee: str | None = None, + heading_col_bottom_tee: str | None = None, ) -> str: """Assembles a line of text in the ascii table @@ -176,17 +233,26 @@ def __row_to_ascii( The line in the ascii table """ output = "" + # wrap long lines in merged cells + if isinstance(filler, list): + filler = self.__wrap_long_lines_in_merged_cells(filler, column_separator) # find the maximum number of lines a single cell in the column has (minimum of 1) num_lines = max(len(str(cell).splitlines()) for cell in filler) or 1 # repeat for each line of text in the cell for line_index in range(num_lines): output += self.__line_in_row_to_ascii( - line_index, - left_edge, - heading_col_sep, - column_separator, - right_edge, - filler, + line_index=line_index, + left_edge=left_edge, + heading_col_sep=heading_col_sep, + column_separator=column_separator, + right_edge=right_edge, + filler=filler, + previous_content_row=previous_content_row, + next_content_row=next_content_row, + top_tee=top_tee, + bottom_tee=bottom_tee, + heading_col_top_tee=heading_col_top_tee, + heading_col_bottom_tee=heading_col_bottom_tee, ) # don't use separation row if it's only space if isinstance(filler, str) and output.strip() == "": @@ -201,6 +267,12 @@ def __line_in_row_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, + top_tee: str | None = None, + bottom_tee: str | None = None, + heading_col_top_tee: str | None = None, + heading_col_bottom_tee: str | None = None, ) -> str: """Assembles a line of text in the ascii table @@ -211,12 +283,18 @@ def __line_in_row_to_ascii( # add columns for col_index in range(self.__columns): output += self.__line_in_cell_column_to_ascii( - line_index, - col_index, - heading_col_sep, - column_separator, - right_edge, - filler, + line_index=line_index, + col_index=col_index, + heading_col_sep=heading_col_sep, + column_separator=column_separator, + right_edge=right_edge, + filler=filler, + previous_content_row=previous_content_row, + next_content_row=next_content_row, + top_tee=top_tee, + bottom_tee=bottom_tee, + heading_col_top_tee=heading_col_top_tee, + heading_col_bottom_tee=heading_col_bottom_tee, ) output += "\n" return output @@ -229,6 +307,12 @@ def __line_in_cell_column_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, + top_tee: str | None = None, + bottom_tee: str | None = None, + heading_col_top_tee: str | None = None, + heading_col_bottom_tee: str | None = None, ) -> str: """Assembles a column of text in the ascii table @@ -236,50 +320,100 @@ def __line_in_cell_column_to_ascii( The column in the ascii table """ output = "" - # content between separators - col_content = "" - # if filler is a separator character, repeat it for the full width of the column - if isinstance(filler, str): - col_content = filler * self.__column_widths[col_index] - # otherwise, use the text from the corresponding column in the filler list - else: - # get the text of the current line in the cell - # if there are fewer lines in the current cell than others, empty string is used - col_lines = str(filler[col_index]).splitlines() - if line_index < len(col_lines): - col_content = col_lines[line_index] - # pad the text to the width of the column using the alignment - col_content = self.__pad( - col_content, - self.__column_widths[col_index], - self.__alignments[col_index], + col_content = ( + # if filler is a separator character, repeat it for the full width of the column + filler * self.__column_widths[col_index] + if isinstance(filler, str) + # otherwise, use the text from the corresponding column in the filler list + else self.__get_padded_cell_line_content( + line_index=line_index, + col_index=col_index, + column_separator=column_separator, + filler=filler, ) + ) output += col_content + # check for merged cells + next_value = prev_row_next_value = next_row_next_value = None + if col_index < self.__columns - 1: + next_value = filler[col_index + 1] if isinstance(filler, list) else None + prev_row_next_value = ( + previous_content_row[col_index + 1] if previous_content_row else None + ) + next_row_next_value = next_content_row[col_index + 1] if next_content_row else None # column separator sep = column_separator - if col_index == 0 and self.__first_col_heading: - # use column heading if first column option is specified - sep = heading_col_sep - elif col_index == self.__columns - 2 and self.__last_col_heading: - # use column heading if last column option is specified + # handle separators between rows when previous or next row is a merged cell + if top_tee and prev_row_next_value is Merge.LEFT: + sep = top_tee + if bottom_tee and next_row_next_value is Merge.LEFT: + sep = bottom_tee + if ( + isinstance(filler, str) + and prev_row_next_value in (Merge.LEFT, None) + and next_row_next_value in (Merge.LEFT, None) + ): + sep = filler + # use column heading if first or last column option is specified + if (col_index == 0 and self.__first_col_heading) or ( + col_index == self.__columns - 2 and self.__last_col_heading + ): sep = heading_col_sep + # handle separators between rows when previous or next row is a merged cell + if heading_col_top_tee and prev_row_next_value is Merge.LEFT: + sep = heading_col_top_tee + if heading_col_bottom_tee and next_row_next_value is Merge.LEFT: + sep = heading_col_bottom_tee + # replace last separator with symbol for edge of the row elif col_index == self.__columns - 1: - # replace last separator with symbol for edge of the row sep = right_edge + # if this is cell contents and the next column is Merge.LEFT, don't add a separator + if next_value is Merge.LEFT: + sep = "" return output + sep + def __get_padded_cell_line_content( + self, line_index: int, col_index: int, column_separator: str, filler: list[SupportsStr] + ) -> str: + # If this is a merge cell, merge with the previous column + if filler[col_index] is Merge.LEFT: + return "" + # get the text of the current line in the cell + # if there are fewer lines in the current cell than others, empty string is used + col_lines = str(filler[col_index]).splitlines() + col_content = col_lines[line_index] if line_index < len(col_lines) else "" + pad_width = self.__column_widths[col_index] + # if the columns to the right are Merge.LEFT, add their width to the padding + for other_col_index in range(col_index + 1, self.__columns): + if filler[other_col_index] is not Merge.LEFT: + break + pad_width += self.__column_widths[other_col_index] + len(column_separator) + # pad the text to the width of the column using the alignment + return self.__pad( + cell_value=col_content, + width=pad_width, + alignment=self.__alignments[col_index], + ) + def __top_edge_to_ascii(self) -> str: """Assembles the top edge of the ascii table Returns: The top edge of the ascii table """ + first_row = self.__body[0] if self.__body else None + first_row = self.__header if self.__header else first_row return self.__row_to_ascii( left_edge=self.__style.top_left_corner, heading_col_sep=self.__style.heading_col_top_tee, column_separator=self.__style.top_tee, right_edge=self.__style.top_right_corner, filler=self.__style.top_and_bottom_edge, + next_content_row=first_row, + top_tee=self.__style.col_row_top_tee, + bottom_tee=self.__style.top_and_bottom_edge, + heading_col_top_tee=self.__style.heading_col_top_tee, + heading_col_bottom_tee=self.__style.top_and_bottom_edge, ) def __bottom_edge_to_ascii(self) -> str: @@ -288,19 +422,26 @@ def __bottom_edge_to_ascii(self) -> str: Returns: The bottom edge of the ascii table """ + last_row = self.__body[-1] if self.__body else None + last_row = self.__footer if self.__footer else last_row return self.__row_to_ascii( left_edge=self.__style.bottom_left_corner, heading_col_sep=self.__style.heading_col_bottom_tee, column_separator=self.__style.bottom_tee, right_edge=self.__style.bottom_right_corner, filler=self.__style.top_and_bottom_edge, + previous_content_row=last_row, + top_tee=self.__style.top_and_bottom_edge, + bottom_tee=self.__style.col_row_bottom_tee, + heading_col_top_tee=self.__style.top_and_bottom_edge, + heading_col_bottom_tee=self.__style.heading_col_bottom_tee, ) - def __heading_row_to_ascii(self, row: list[SupportsStr]) -> str: - """Assembles the header or footer row line of the ascii table + def __content_row_to_ascii(self, row: list[SupportsStr]) -> str: + """Assembles a row of cell values into a single line of the ascii table Returns: - The header or footer row line of the ascii table + The row of the ascii table """ return self.__row_to_ascii( left_edge=self.__style.left_and_right_edge, @@ -310,7 +451,11 @@ def __heading_row_to_ascii(self, row: list[SupportsStr]) -> str: filler=row, ) - def __heading_sep_to_ascii(self) -> str: + def __heading_sep_to_ascii( + self, + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, + ) -> str: """Assembles the separator below the header or above footer of the ascii table Returns: @@ -322,6 +467,12 @@ def __heading_sep_to_ascii(self) -> str: column_separator=self.__style.heading_row_cross, right_edge=self.__style.heading_row_right_tee, filler=self.__style.heading_row_sep, + previous_content_row=previous_content_row, + next_content_row=next_content_row, + top_tee=self.__style.heading_row_top_tee, + bottom_tee=self.__style.heading_row_bottom_tee, + heading_col_top_tee=self.__style.heading_col_heading_row_top_tee, + heading_col_bottom_tee=self.__style.heading_col_heading_row_bottom_tee, ) def __body_to_ascii(self, body: list[list[SupportsStr]]) -> str: @@ -330,23 +481,26 @@ def __body_to_ascii(self, body: list[list[SupportsStr]]) -> str: Returns: The body of the ascii table """ - separation_row = self.__row_to_ascii( - left_edge=self.__style.row_left_tee, - heading_col_sep=self.__style.heading_col_row_cross, - column_separator=self.__style.col_row_cross, - right_edge=self.__style.row_right_tee, - filler=self.__style.row_sep, - ) - return separation_row.join( - self.__row_to_ascii( - left_edge=self.__style.left_and_right_edge, - heading_col_sep=self.__style.heading_col_sep, - column_separator=self.__style.col_sep, - right_edge=self.__style.left_and_right_edge, - filler=row, + # first content row + output = self.__content_row_to_ascii(body[0]) if len(body) else "" + for row_index, row in enumerate(body[1:], 1): + # separator between rows + output += self.__row_to_ascii( + left_edge=self.__style.row_left_tee, + heading_col_sep=self.__style.heading_col_row_cross, + column_separator=self.__style.col_row_cross, + right_edge=self.__style.row_right_tee, + filler=self.__style.row_sep, + previous_content_row=body[row_index - 1], + next_content_row=row, + top_tee=self.__style.col_row_top_tee, + bottom_tee=self.__style.col_row_bottom_tee, + heading_col_top_tee=self.__style.heading_col_body_row_top_tee, + heading_col_bottom_tee=self.__style.heading_col_body_row_bottom_tee, ) - for row in body - ) + # content row + output += self.__content_row_to_ascii(row) + return output def __str_width(self, text: str) -> int: """ @@ -375,15 +529,21 @@ def to_ascii(self) -> str: table = self.__top_edge_to_ascii() # add table header if self.__header: - table += self.__heading_row_to_ascii(self.__header) - table += self.__heading_sep_to_ascii() + table += self.__content_row_to_ascii(self.__header) + table += self.__heading_sep_to_ascii( + previous_content_row=self.__header, + next_content_row=self.__body[0] if self.__body else None, + ) # add table body if self.__body: table += self.__body_to_ascii(self.__body) # add table footer if self.__footer: - table += self.__heading_sep_to_ascii() - table += self.__heading_row_to_ascii(self.__footer) + table += self.__heading_sep_to_ascii( + previous_content_row=self.__body[-1] if self.__body else None, + next_content_row=self.__footer, + ) + table += self.__content_row_to_ascii(self.__footer) # bottom row of table table += self.__bottom_edge_to_ascii() # reurn ascii table @@ -430,7 +590,7 @@ def table2ascii( separator. If set to ``0``, the cell content will be flush against the column separator. Defaults to ``1``. style: Table style to use for styling (preset styles can be imported). - Defaults to :ref:`PresetStyle.double_thin_compact `. + Defaults to :ref:`PresetStyle.double_thin_compact`. use_wcwidth: Whether to use :func:`wcwidth.wcswidth` to determine the width of each cell instead of :func:`len`. The :func:`~wcwidth.wcswidth` function takes into account double-width characters (East Asian Wide and East Asian Fullwidth) and zero-width characters (combining characters, diff --git a/tests/test_merge.py b/tests/test_merge.py new file mode 100644 index 0000000..3555ce9 --- /dev/null +++ b/tests/test_merge.py @@ -0,0 +1,175 @@ +from table2ascii import Alignment, Merge, PresetStyle, table2ascii as t2a + + +def test_merge_all_edges(): + text = t2a( + header=["Header", Merge.LEFT, "A", "B"], + body=[ + ["A", "B", "C", Merge.LEFT], + ["D", Merge.LEFT, "E", Merge.LEFT], + ["F", "G", "H", "I"], + ["J", "K", "L", Merge.LEFT], + ], + column_widths=[4, 4, None, None], + footer=["Footer", Merge.LEFT, "A", "B"], + style=PresetStyle.double_thin_box, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•คโ•โ•โ•โ•—\n" + "โ•‘ Header โ”‚ A โ”‚ B โ•‘\n" + "โ• โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•ชโ•โ•โ•โ•งโ•โ•โ•โ•ฃ\n" + "โ•‘ A โ”‚ B โ”‚ C โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ D โ”‚ E โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ•ข\n" + "โ•‘ F โ”‚ G โ”‚ H โ”‚ I โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ•ข\n" + "โ•‘ J โ”‚ K โ”‚ L โ•‘\n" + "โ• โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•ฃ\n" + "โ•‘ Footer โ”‚ A โ”‚ B โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•งโ•โ•โ•โ•" + ) + assert text == expected + + +def test_merge_no_header_column(): + text = t2a( + header=["#", "G", "Merge", Merge.LEFT, "S"], + body=[ + [1, 5, 6, 200, Merge.LEFT], + [2, "E", "Long cell", Merge.LEFT, Merge.LEFT], + ["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"], + ], + footer=["SUM", "100", "200", Merge.LEFT, "300"], + style=PresetStyle.double_thin_box, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ”‚ G โ”‚ Merge โ”‚ S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ”‚ 5 โ”‚ 6 โ”‚ 200 โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ 2 โ”‚ E โ”‚ Long cell โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ Bonus โ”‚ F โ”‚ G โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ”‚ 100 โ”‚ 200 โ”‚ 300 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_merge_header_column(): + text = t2a( + header=["#", "G", "Merge", Merge.LEFT, "S"], + body=[ + [1, 5, 6, 200, Merge.LEFT], + [2, "E", "Long cell", Merge.LEFT, Merge.LEFT], + ["Bonus", Merge.LEFT, Merge.LEFT, "F", "G"], + ], + footer=["SUM", "100", "200", Merge.LEFT, "300"], + style=PresetStyle.double_thin_box, + first_col_heading=True, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G โ”‚ Merge โ”‚ S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ•‘ 5 โ”‚ 6 โ”‚ 200 โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ 2 โ•‘ E โ”‚ Long cell โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•จโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ Bonus โ”‚ F โ”‚ G โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•งโ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ•‘ 100 โ”‚ 200 โ”‚ 300 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_merge_line_wrap(): + text = t2a( + header=["Name", "Price", "Category", "Stock", "Sku"], + body=[ + ["test", 443, "test", 67, "test"], + ], + footer=[ + "Description", + "Long cell value that is merged and wraps to multiple lines", + Merge.LEFT, + Merge.LEFT, + Merge.LEFT, + ], + alignments=[Alignment.LEFT] * 5, + style=PresetStyle.double_thin_box, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•—\n" + "โ•‘ Name โ”‚ Price โ”‚ Category โ”‚ Stock โ”‚ Sku โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ test โ”‚ 443 โ”‚ test โ”‚ 67 โ”‚ test โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ Description โ”‚ Long cell value that is merged โ•‘\n" + "โ•‘ โ”‚ and wraps to multiple lines โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_merge_compact(): + text = t2a( + header=["Header", Merge.LEFT, "A", "B"], + body=[ + ["A", "B", "C", Merge.LEFT], + ["D", Merge.LEFT, "E", Merge.LEFT], + ["F", "G", "H", "I"], + ["J", "K", "L", Merge.LEFT], + ], + footer=["Footer", Merge.LEFT, "A", "B"], + column_widths=[4, 4, None, None], + style=PresetStyle.double_thin_compact, + first_col_heading=True, + ) + expected = ( + "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" + "โ•‘ Header A B โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ•ฅโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ A โ•‘ B C โ•‘\n" + "โ•‘ D E โ•‘\n" + "โ•‘ F โ•‘ G H I โ•‘\n" + "โ•‘ J โ•‘ K L โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ•จโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ Footer A B โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" + ) + assert text == expected + + +def test_row_beginning_with_merge(): + text = t2a( + header=[Merge.LEFT, "A", "B", Merge.LEFT], + body=[ + [Merge.LEFT, "A", "B", "C"], + [Merge.LEFT, Merge.LEFT, Merge.LEFT, Merge.LEFT], + ["F", "G", "H", "I"], + ["J", "K", "L", Merge.LEFT], + ], + footer=[Merge.LEFT, Merge.LEFT, "A", "B"], + style=PresetStyle.double_thin_box, + ) + expected = ( + "โ•”โ•โ•โ•โ•คโ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•โ•โ•—\n" + "โ•‘ โ”‚ A โ”‚ B โ•‘\n" + "โ• โ•โ•โ•โ•ชโ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•ฃ\n" + "โ•‘ โ”‚ A โ”‚ B โ”‚ C โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ•ข\n" + "โ•‘ โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ•ข\n" + "โ•‘ F โ”‚ G โ”‚ H โ”‚ I โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ•ข\n" + "โ•‘ J โ”‚ K โ”‚ L โ•‘\n" + "โ• โ•โ•โ•โ•งโ•โ•โ•โ•ชโ•โ•โ•โ•คโ•โ•โ•โ•ฃ\n" + "โ•‘ โ”‚ A โ”‚ B โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•งโ•โ•โ•โ•" + ) + assert text == expected diff --git a/tests/test_styles.py b/tests/test_styles.py index 422f0c1..9a80323 100644 --- a/tests/test_styles.py +++ b/tests/test_styles.py @@ -1,665 +1,109 @@ -from table2ascii import PresetStyle, table2ascii as t2a +import pytest - -def test_thin(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin, - ) - expected = ( - "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" - "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - ) - assert text == expected +from table2ascii import PresetStyle, TableStyle, table2ascii as t2a +from table2ascii.exceptions import TableStyleTooLongError, TableStyleTooShortWarning -def test_thin_box(): - text = t2a( +def _build_table_with_style(table_style: TableStyle) -> str: + return t2a( header=["#", "G", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], footer=["SUM", "130", "140", "135", "130"], first_col_heading=True, last_col_heading=False, - style=PresetStyle.thin_box, + style=table_style, ) - expected = ( - "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”\n" - "โ”‚ # โ”‚ G โ”‚ H โ”‚ R โ”‚ S โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 1 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 2 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ SUM โ”‚ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ”‚\n" - "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜" - ) - assert text == expected -def test_thin_rounded(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_rounded, - ) +def test_table_style_from_string(): + table_style = TableStyle.from_string("โ•”โ•โ•ฆโ•คโ•—โ•‘โ•‘โ”‚โ• โ•โ•ฌโ•ชโ•ฃโ•Ÿโ”€โ•ซโ”ผโ•ขโ•šโ•ฉโ•งโ•โ”ฌโ”ดโ•คโ•งโ•ฅโ•จโ•ฆโ•ฉ") + text = _build_table_with_style(table_style) expected = ( - "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" - "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G โ”‚ H โ”‚ R โ”‚ S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ 2 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ•‘ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ•‘\n" + "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•" ) assert text == expected -def test_thin_compact(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_compact, - ) - expected = ( - "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" - "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" - "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - ) - assert text == expected +def test_table_style_from_string_too_long_error(): + with pytest.raises(TableStyleTooLongError): + TableStyle.from_string("โ•”โ•โ•ฆโ•คโ•—โ•‘โ•‘โ”‚โ• โ•โ•ฌโ•ชโ•ฃโ•Ÿโ”€โ•ซโ”ผโ•ขโ•šโ•ฉโ•งโ•โ”ฌโ”ดโ•คโ•งโ•ฅโ•จโ•ฆโ•ฉโ•ฆXXXX") -def test_thin_compact_rounded(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_compact_rounded, - ) +def test_table_style_from_string_too_short(): + with pytest.warns(TableStyleTooShortWarning): + table_style = TableStyle.from_string("โ•”โ•โ•ฆโ•คโ•—โ•‘โ•‘โ”‚โ• โ•โ•ฌโ•ชโ•ฃโ•Ÿโ”€โ•ซโ”ผโ•ข") + text = _build_table_with_style(table_style) expected = ( - "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" - "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" - "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•คโ•โ•โ•โ•โ•โ•—\n" + "โ•‘ # โ•‘ G โ”‚ H โ”‚ R โ”‚ S โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ 1 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘\n" + "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ•ข\n" + "โ•‘ 2 โ•‘ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ•‘\n" + "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•ฃ\n" + "โ•‘ SUM โ•‘ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ•‘\n" + " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• " ) assert text == expected -def test_thin_thick(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_thick, - ) +def test_thin(): + text = _build_table_with_style(PresetStyle.thin) expected = ( "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" ) assert text == expected -def test_thin_thick_rounded(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_thick_rounded, - ) +def test_thin_box(): + text = _build_table_with_style(PresetStyle.thin_box) expected = ( - "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" - "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" - "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ”โ”โ”โ”โ”โ”โ”ฟโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฅ\n" - "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" + "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”\n" + "โ”‚ # โ”‚ G โ”‚ H โ”‚ R โ”‚ S โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 1 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ 2 โ”‚ 30 โ”‚ 40 โ”‚ 35 โ”‚ 30 โ”‚\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”ค\n" + "โ”‚ SUM โ”‚ 130 โ”‚ 140 โ”‚ 135 โ”‚ 130 โ”‚\n" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”˜" ) assert text == expected -def test_thin_double(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_double, - ) +def test_thin_compact(): + text = text = _build_table_with_style(PresetStyle.thin_compact) expected = ( "โ”Œโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" - "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" - "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" - "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - ) - assert text == expected - - -def test_thin_double_rounded(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thin_double_rounded, - ) - expected = ( - "โ•ญโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n" - "โ”‚ # โ”‚ G H R S โ”‚\n" - "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" "โ”‚ 1 โ”‚ 30 40 35 30 โ”‚\n" - "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" "โ”‚ 2 โ”‚ 30 40 35 30 โ”‚\n" - "โ•žโ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก\n" + "โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n" "โ”‚ SUM โ”‚ 130 140 135 130 โ”‚\n" - "โ•ฐโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ" - ) - assert text == expected - - -def test_thick(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thick, - ) - expected = ( - "โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\n" - "โ”ƒ # โ”ƒ G H R S โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ SUM โ”ƒ 130 140 135 130 โ”ƒ\n" - "โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›" - ) - assert text == expected - - -def test_thick_box(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thick_box, - ) - expected = ( - "โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”“\n" - "โ”ƒ # โ”ƒ G โ”ƒ H โ”ƒ R โ”ƒ S โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ 1 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ 2 โ”ƒ 30 โ”ƒ 40 โ”ƒ 35 โ”ƒ 30 โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ SUM โ”ƒ 130 โ”ƒ 140 โ”ƒ 135 โ”ƒ 130 โ”ƒ\n" - "โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”›" - ) - assert text == expected - - -def test_thick_compact(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.thick_compact, - ) - expected = ( - "โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“\n" - "โ”ƒ # โ”ƒ G H R S โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ 1 โ”ƒ 30 40 35 30 โ”ƒ\n" - "โ”ƒ 2 โ”ƒ 30 40 35 30 โ”ƒ\n" - "โ”ฃโ”โ”โ”โ”โ”โ•‹โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ซ\n" - "โ”ƒ SUM โ”ƒ 130 140 135 130 โ”ƒ\n" - "โ”—โ”โ”โ”โ”โ”โ”ปโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”›" - ) - assert text == expected - - -def test_double(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.double, - ) - expected = ( - "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" - "โ•‘ # โ•‘ G H R S โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" - ) - assert text == expected - - -def test_double_box(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.double_box, - ) - expected = ( - "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•—\n" - "โ•‘ # โ•‘ G โ•‘ H โ•‘ R โ•‘ S โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ 1 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ 2 โ•‘ 30 โ•‘ 40 โ•‘ 35 โ•‘ 30 โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ SUM โ•‘ 130 โ•‘ 140 โ•‘ 135 โ•‘ 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•" - ) - assert text == expected - - -def test_double_compact(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.double_compact, - ) - expected = ( - "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" - "โ•‘ # โ•‘ G H R S โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" - "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ• โ•โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n" - "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" - ) - assert text == expected - - -def test_double_thin_compact(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.double_thin_compact, - ) - expected = ( - "โ•”โ•โ•โ•โ•โ•โ•ฆโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n" - "โ•‘ # โ•‘ G H R S โ•‘\n" - "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" - "โ•‘ 1 โ•‘ 30 40 35 30 โ•‘\n" - "โ•‘ 2 โ•‘ 30 40 35 30 โ•‘\n" - "โ•Ÿโ”€โ”€โ”€โ”€โ”€โ•ซโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ข\n" - "โ•‘ SUM โ•‘ 130 140 135 130 โ•‘\n" - "โ•šโ•โ•โ•โ•โ•โ•ฉโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•" - ) - assert text == expected - - -def test_minimalist(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.minimalist, - ) - expected = ( - " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ \n" - " # โ”‚ G H R S \n" - " โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” \n" - " 1 โ”‚ 30 40 35 30 \n" - " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ \n" - " 2 โ”‚ 30 40 35 30 \n" - " โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” \n" - " SUM โ”‚ 130 140 135 130 \n" - " โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ " - ) - assert text == expected - - -def test_borderless(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.borderless, - ) - expected = ( - " # โ”ƒ G H R S \n" - " โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” \n" - " 1 โ”ƒ 30 40 35 30 \n" - " 2 โ”ƒ 30 40 35 30 \n" - " โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” โ”โ”โ”โ”โ” \n" - " SUM โ”ƒ 130 140 135 130 " - ) - assert text == expected - - -def test_simple(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.simple, - ) - expected = ( - " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• \n" - " # โ•‘ G H R S \n" - " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• \n" - " 1 โ•‘ 30 40 35 30 \n" - " 2 โ•‘ 30 40 35 30 \n" - " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• \n" - " SUM โ•‘ 130 140 135 130 \n" - " โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• โ•โ•โ•โ•โ• " - ) - assert text == expected - - -def test_ascii(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii, - ) - expected = ( - "+-----+-----------------------+\n" - "| # | G H R S |\n" - "+-----+-----------------------+\n" - "| 1 | 30 40 35 30 |\n" - "+-----+-----------------------+\n" - "| 2 | 30 40 35 30 |\n" - "+-----+-----------------------+\n" - "| SUM | 130 140 135 130 |\n" - "+-----+-----------------------+" - ) - assert text == expected - - -def test_ascii_box(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_box, - ) - expected = ( - "+-----+-----+-----+-----+-----+\n" - "| # | G | H | R | S |\n" - "+-----+-----+-----+-----+-----+\n" - "| 1 | 30 | 40 | 35 | 30 |\n" - "+-----+-----+-----+-----+-----+\n" - "| 2 | 30 | 40 | 35 | 30 |\n" - "+-----+-----+-----+-----+-----+\n" - "| SUM | 130 | 140 | 135 | 130 |\n" - "+-----+-----+-----+-----+-----+" - ) - assert text == expected - - -def test_ascii_compact(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_compact, - ) - expected = ( - "+-----+-----------------------+\n" - "| # | G H R S |\n" - "+-----+-----------------------+\n" - "| 1 | 30 40 35 30 |\n" - "| 2 | 30 40 35 30 |\n" - "+-----+-----------------------+\n" - "| SUM | 130 140 135 130 |\n" - "+-----+-----------------------+" - ) - assert text == expected - - -def test_ascii_double(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_double, - ) - expected = ( - "+-----+-----------------------+\n" - "| # | G H R S |\n" - "+=====+=======================+\n" - "| 1 | 30 40 35 30 |\n" - "+-----+-----------------------+\n" - "| 2 | 30 40 35 30 |\n" - "+=====+=======================+\n" - "| SUM | 130 140 135 130 |\n" - "+-----+-----------------------+" - ) - assert text == expected - - -def test_ascii_minimalist(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_minimalist, - ) - expected = ( - " ----------------------------- \n" - " # | G H R S \n" - " ============================= \n" - " 1 | 30 40 35 30 \n" - " ----------------------------- \n" - " 2 | 30 40 35 30 \n" - " ============================= \n" - " SUM | 130 140 135 130 \n" - " ----------------------------- " - ) - assert text == expected - - -def test_ascii_borderless(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_borderless, - ) - expected = ( - " # | G H R S \n" - " ----- ----- ----- ----- ----- \n" - " 1 | 30 40 35 30 \n" - " 2 | 30 40 35 30 \n" - " ----- ----- ----- ----- ----- \n" - " SUM | 130 140 135 130 " - ) - assert text == expected - - -def test_ascii_rounded(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_rounded, - ) - expected = ( - "/=============================\\\n" - "| # | G H R S |\n" - "|=====|=======================|\n" - "| 1 | 30 40 35 30 |\n" - "|-----|-----------------------|\n" - "| 2 | 30 40 35 30 |\n" - "|=====|=======================|\n" - "| SUM | 130 140 135 130 |\n" - "\\=====|=======================/" - ) - assert text == expected - - -def test_ascii_rounded_box(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_rounded_box, - ) - expected = ( - "/=============================\\\n" - "| # | G | H | R | S |\n" - "|=====|=====|=====|=====|=====|\n" - "| 1 | 30 | 40 | 35 | 30 |\n" - "|-----|-----|-----|-----|-----|\n" - "| 2 | 30 | 40 | 35 | 30 |\n" - "|=====|=====|=====|=====|=====|\n" - "| SUM | 130 | 140 | 135 | 130 |\n" - "\\=====|=====|=====|=====|=====/" - ) - assert text == expected - - -def test_ascii_simple(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.ascii_simple, - ) - expected = ( - " ===== ===== ===== ===== ===== \n" - " # | G H R S \n" - " ===== ===== ===== ===== ===== \n" - " 1 | 30 40 35 30 \n" - " 2 | 30 40 35 30 \n" - " ===== ===== ===== ===== ===== \n" - " SUM | 130 140 135 130 \n" - " ===== ===== ===== ===== ===== " - ) - assert text == expected - - -def test_markdown(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.markdown, - ) - expected = ( - "| # | G | H | R | S |\n" - "|-----|-----|-----|-----|-----|\n" - "| 1 | 30 | 40 | 35 | 30 |\n" - "| 2 | 30 | 40 | 35 | 30 |\n" - "|-----|-----|-----|-----|-----|\n" - "| SUM | 130 | 140 | 135 | 130 |" + "โ””โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" ) assert text == expected def test_plain(): - text = t2a( - header=["#", "G", "H", "R", "S"], - body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], - footer=["SUM", "130", "140", "135", "130"], - first_col_heading=True, - last_col_heading=False, - style=PresetStyle.plain, - ) + text = _build_table_with_style(PresetStyle.plain) expected = ( " # G H R S \n" " 1 30 40 35 30 \n"