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" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy