From 99648524a3b194418bf83bbee73a3fd706fb865d Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 17:17:15 -0700 Subject: [PATCH 01/27] feat: Initial Merge.LEFT implementation --- table2ascii/merge.py | 30 +++++++++++ table2ascii/table_to_ascii.py | 95 ++++++++++++++++++++++------------- 2 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 table2ascii/merge.py diff --git a/table2ascii/merge.py b/table2ascii/merge.py new file mode 100644 index 0000000..a85fef4 --- /dev/null +++ b/table2ascii/merge.py @@ -0,0 +1,30 @@ +from enum import Enum + + +class Merge(Enum): + """Enum for types of cell merging in a table + + Example:: + + output = table2ascii( + body=[ + ["a", "b", "c", "d"], + ["e", "Long cell value", Merge.LEFT, Merge.LEFT], + ], + ) + + print(output) + + ╔═════════════════════╗ + ║ a b c d ║ + ║ e Long cell value ║ + ╚═════════════════════╝ + """ + + LEFT = 0 + + def __repr__(self): + return f"Merge.{self.name}" + + def __str__(self): + return "" diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index a8ff2c6..290b4ba 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -13,6 +13,7 @@ InvalidAlignmentError, InvalidCellPaddingError, ) +from .merge import Merge from .options import Options from .preset_style import PresetStyle from .table_style import TableStyle @@ -95,13 +96,21 @@ def widest_line(value: SupportsStr) -> int: text = str(value) return max(len(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 @@ -232,38 +241,61 @@ 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, col_index, column_separator, filler ) + ) output += col_content # column separator sep = column_separator + # use column heading if first column option is specified if col_index == 0 and self.__first_col_heading: - # use column heading if first column option is specified sep = heading_col_sep + # use column heading if last column option is specified elif col_index == self.__columns - 2 and self.__last_col_heading: - # use column heading if last column option is specified sep = heading_col_sep + # 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 + next_value = ( + filler[col_index + 1] + if not isinstance(filler, str) and col_index < self.__columns - 1 + else None + ) + if next_value is Merge.LEFT: + sep = "" + # TODO: handle alternate separators between rows when row above or below is merged 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 @@ -292,7 +324,7 @@ def __bottom_edge_to_ascii(self) -> str: filler=self.__style.top_and_bottom_edge, ) - def __heading_row_to_ascii(self, row: list[SupportsStr]) -> str: + def __content_row_to_ascii(self, row: list[SupportsStr]) -> str: """Assembles the header or footer row line of the ascii table Returns: @@ -333,16 +365,7 @@ def __body_to_ascii(self, body: list[list[SupportsStr]]) -> str: 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, - ) - for row in body - ) + return separation_row.join(self.__content_row_to_ascii(row) for row in body) def to_ascii(self) -> str: """Generates a formatted ASCII table @@ -354,7 +377,7 @@ 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.__content_row_to_ascii(self.__header) table += self.__heading_sep_to_ascii() # add table body if self.__body: @@ -362,7 +385,7 @@ def to_ascii(self) -> str: # add table footer if self.__footer: table += self.__heading_sep_to_ascii() - table += self.__heading_row_to_ascii(self.__footer) + table += self.__content_row_to_ascii(self.__footer) # bottom row of table table += self.__bottom_edge_to_ascii() # reurn ascii table From b91e5a0a05bb0fecbb009d79dd50dd06479da568 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 17:52:33 -0700 Subject: [PATCH 02/27] Wrap long lines in merged cells --- table2ascii/table_to_ascii.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 290b4ba..91b78e4 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 .alignment import Alignment @@ -167,6 +168,33 @@ def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> st return (" " * (width - len(padded_text))) + 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) + wrapped_row.append(cell) + return wrapped_row + def __row_to_ascii( self, left_edge: str, @@ -181,6 +209,9 @@ 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 From d36a2d8c6203e8fc3ca93c57473a5b81a1479e3a Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 17:55:46 -0700 Subject: [PATCH 03/27] Fix wrap edge case --- table2ascii/table_to_ascii.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 91b78e4..2fd425c 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -191,7 +191,7 @@ def __wrap_long_lines_in_merged_cells( 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) + cell = textwrap.fill(str(cell), merged_width - self.__cell_padding * 2) wrapped_row.append(cell) return wrapped_row From f7f5db90638b9c7bc610f745b6150cf49e406629 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 19:33:32 -0700 Subject: [PATCH 04/27] feat!: Merge cell for table styles --- table2ascii/preset_style.py | 60 +++++++++--------- table2ascii/table_style.py | 33 ++++++++++ table2ascii/table_to_ascii.py | 113 ++++++++++++++++++++++++++++------ 3 files changed, 156 insertions(+), 50 deletions(-) diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py index 486ea1c..5c597aa 100644 --- a/table2ascii/preset_style.py +++ b/table2ascii/preset_style.py @@ -16,33 +16,33 @@ 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_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="") diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 53cc4b5..b3ab8a9 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -33,6 +33,23 @@ class TableStyle: ╠═════╬═════╪═════╪═════╪═════╣ ║ SUM ║ 130 │ 140 │ 135 │ 130 ║ ╚═════╩═════╩═════╩═════╩═════╝ + + In addition to the parts above, W-Z are used for merged cells as follows: + + .. code-block:: + + +-----+-----+-------+-----+ + | # | G | Merge | S | + +=====+=====+==[Y]==+=====+ + | 1 | 5 | 6 | 4 | 5 | + +-----+-----+--[X]-[X]----+ + | 2 | E | Long cell | + +----[X]---[X]-[W]--+-----+ + | Bonus | F | G | + +====[Y]===[Y]=[Z]==+=====+ + | SUM | 100 | 200 | 300 | + +-----+-----+-------+-----+ + """ # parts of the table @@ -58,6 +75,10 @@ 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 @classmethod def from_string(cls, string: str) -> "TableStyle": @@ -72,7 +93,19 @@ def from_string(cls, string: str) -> "TableStyle": Example:: TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝") + + Raises: + ValueError: 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 ValueError( + f"Too many characters in string. Expected {num_params} but got {len(string)}." + ) + # if the string is too short, pad it with spaces + elif len(string) < num_params: + string = f"{string}{' ' * (num_params - len(string))}" return cls(*string) def set(self, **kwargs) -> "TableStyle": diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 2fd425c..3d22810 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -202,6 +202,10 @@ def __row_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], + top_tee: str | None = None, + bottom_tee: str | None = None, + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, ) -> str: """Assembles a line of text in the ascii table @@ -223,6 +227,10 @@ def __row_to_ascii( column_separator, right_edge, filler, + top_tee, + bottom_tee, + previous_content_row, + next_content_row, ) # don't use separation row if it's only space if isinstance(filler, str) and output.strip() == "": @@ -237,6 +245,10 @@ def __line_in_row_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], + top_tee: str | None = None, + bottom_tee: str | None = None, + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, ) -> str: """Assembles a line of text in the ascii table @@ -253,6 +265,10 @@ def __line_in_row_to_ascii( column_separator, right_edge, filler, + top_tee, + bottom_tee, + previous_content_row, + next_content_row, ) output += "\n" return output @@ -265,6 +281,10 @@ def __line_in_cell_column_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], + top_tee: str | None = None, + bottom_tee: str | None = None, + previous_content_row: list[SupportsStr] | None = None, + next_content_row: list[SupportsStr] | None = None, ) -> str: """Assembles a column of text in the ascii table @@ -282,8 +302,36 @@ def __line_in_cell_column_to_ascii( ) ) output += col_content + # check for merged cells + next_value = ( + filler[col_index + 1] + if not isinstance(filler, str) and col_index < self.__columns - 1 + else None + ) + prev_row_next_value = ( + previous_content_row[col_index + 1] + if previous_content_row is not None and col_index < self.__columns - 1 + else None + ) + next_row_next_value = ( + next_content_row[col_index + 1] + if next_content_row is not None and col_index < self.__columns - 1 + else None + ) # column separator sep = column_separator + # if this is cell contents and the next column is Merge.LEFT, don't add a separator + if next_value is Merge.LEFT: + sep = "" + # handle separators between rows when previous or next row is a merged cell + elif isinstance(filler, str): + empty = (Merge.LEFT, None) # values indicating a merged cell or end of the table + 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 prev_row_next_value in empty and next_row_next_value in empty: + sep = filler # use column heading if first column option is specified if col_index == 0 and self.__first_col_heading: sep = heading_col_sep @@ -293,15 +341,6 @@ def __line_in_cell_column_to_ascii( # replace last separator with symbol for edge of the row elif col_index == self.__columns - 1: sep = right_edge - # if this is cell contents and the next column is Merge.LEFT, don't add a separator - next_value = ( - filler[col_index + 1] - if not isinstance(filler, str) and col_index < self.__columns - 1 - else None - ) - if next_value is Merge.LEFT: - sep = "" - # TODO: handle alternate separators between rows when row above or below is merged return output + sep def __get_padded_cell_line_content( @@ -333,12 +372,17 @@ def __top_edge_to_ascii(self) -> str: 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, + top_tee=self.__style.col_row_top_tee, + bottom_tee=self.__style.col_row_bottom_tee, + next_content_row=first_row, ) def __bottom_edge_to_ascii(self) -> str: @@ -347,12 +391,17 @@ 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, + top_tee=self.__style.col_row_top_tee, + bottom_tee=self.__style.col_row_bottom_tee, + previous_content_row=last_row, ) def __content_row_to_ascii(self, row: list[SupportsStr]) -> str: @@ -369,7 +418,11 @@ def __content_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: @@ -381,6 +434,10 @@ 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, + top_tee=self.__style.heading_row_top_tee, + bottom_tee=self.__style.heading_row_bottom_tee, + previous_content_row=previous_content_row, + next_content_row=next_content_row, ) def __body_to_ascii(self, body: list[list[SupportsStr]]) -> str: @@ -389,14 +446,24 @@ 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.__content_row_to_ascii(row) for row in body) + # 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, + top_tee=self.__style.col_row_top_tee, + bottom_tee=self.__style.col_row_bottom_tee, + previous_content_row=body[row_index - 1], + next_content_row=row, + ) + # content row + output += self.__content_row_to_ascii(row) + return output def to_ascii(self) -> str: """Generates a formatted ASCII table @@ -409,13 +476,19 @@ def to_ascii(self) -> str: # add table header if self.__header: table += self.__content_row_to_ascii(self.__header) - table += self.__heading_sep_to_ascii() + 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_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() From e427da26a4a976eebef02f6620a90d45e9426ea0 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 20:09:23 -0700 Subject: [PATCH 05/27] Update table_style.py --- table2ascii/table_style.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index b3ab8a9..639b3c2 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -38,15 +38,13 @@ class TableStyle: .. code-block:: - +-----+-----+-------+-----+ - | # | G | Merge | S | - +=====+=====+==[Y]==+=====+ - | 1 | 5 | 6 | 4 | 5 | - +-----+-----+--[X]-[X]----+ - | 2 | E | Long cell | - +----[X]---[X]-[W]--+-----+ - | Bonus | F | G | - +====[Y]===[Y]=[Z]==+=====+ + +-----+-----+---+---+-----+ + | Merge | # | G | S | + +====[Y]====+==[Z]==+=====+ + | 1 | 100 | Long | 150 | + +----[X]----+--[W]--+-----+ + | Bonus | 5 | 5 | 150 | + +====[Y]====+==[Z]==+=====+ | SUM | 100 | 200 | 300 | +-----+-----+-------+-----+ @@ -92,7 +90,7 @@ def from_string(cls, string: str) -> "TableStyle": Example:: - TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝") + TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝ ──") Raises: ValueError: If the string is too long @@ -105,7 +103,7 @@ def from_string(cls, string: str) -> "TableStyle": ) # if the string is too short, pad it with spaces elif len(string) < num_params: - string = f"{string}{' ' * (num_params - len(string))}" + string += " " * (num_params - len(string)) return cls(*string) def set(self, **kwargs) -> "TableStyle": From e4b7aa0a408f4b2570179e7e6a7ae895c835a213 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 20:11:17 -0700 Subject: [PATCH 06/27] Update merge.py --- table2ascii/merge.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/table2ascii/merge.py b/table2ascii/merge.py index a85fef4..f436b7f 100644 --- a/table2ascii/merge.py +++ b/table2ascii/merge.py @@ -6,19 +6,22 @@ class Merge(Enum): Example:: + from table2ascii import table2ascii + from table2ascii.merge import Merge + output = table2ascii( body=[ - ["a", "b", "c", "d"], - ["e", "Long cell value", Merge.LEFT, Merge.LEFT], + ["A", "B", "C", "D"], + ["E", "Long cell", Merge.LEFT, Merge.LEFT], ], ) print(output) - ╔═════════════════════╗ - ║ a b c d ║ - ║ e Long cell value ║ - ╚═════════════════════╝ + ╔═══════════════╗ + ║ A B C D ║ + ║ E Long cell ║ + ╚═══════════════╝ """ LEFT = 0 From b32822c9dae3ba4adbf06589a459801a01bb0bd3 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 20:12:45 -0700 Subject: [PATCH 07/27] Update __init__.py --- table2ascii/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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", ] From 0fe2f71b6846e77209ca52c10338a68da71651c6 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 20:32:11 -0700 Subject: [PATCH 08/27] Update documentation --- docs/source/api.rst | 6 ++++++ table2ascii/merge.py | 44 +++++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index b890a84..280bd18 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -15,6 +15,12 @@ Alignment .. autoenum:: Alignment :members: +Merge +~~~~~ + +.. autoenum:: Merge + :members: + PresetStyle ~~~~~~~~~~~ diff --git a/table2ascii/merge.py b/table2ascii/merge.py index f436b7f..e9e0b6f 100644 --- a/table2ascii/merge.py +++ b/table2ascii/merge.py @@ -2,32 +2,38 @@ class Merge(Enum): - """Enum for types of cell merging in a table + """Enum for merging table cells - Example:: - - from table2ascii import table2ascii - from table2ascii.merge import Merge + Using :attr:`Merge.LEFT` in a table cell will merge the cell it is used in + with the cell to its left. - output = table2ascii( - body=[ - ["A", "B", "C", "D"], - ["E", "Long cell", Merge.LEFT, Merge.LEFT], - ], - ) + In the case that the contents of the merged cell are longer than the + contents of the unmerged cells in the rows above and below, the merged + cell will be wrapped onto multiple lines. - print(output) + Example:: - ╔═══════════════╗ - ║ A B C D ║ - ║ E Long cell ║ - ╚═══════════════╝ + from table2ascii import table2ascii, Merge, PresetStyle + + table2ascii( + body=[ + ["A", "B", "C", "D"], + ["E", "Long cell contents", Merge.LEFT, Merge.LEFT], + ], + style=PresetStyle.double_box, + ) + + \"\"\" + ╔═══╦═══╦═══╦═══╗ + ║ A ║ B ║ C ║ D ║ + ╠═══╬═══╩═══╩═══╣ + ║ E ║ Long cell ║ + ║ ║ contents ║ + ╚═══╩═══════════╝ + \"\"\" """ LEFT = 0 - def __repr__(self): - return f"Merge.{self.name}" - def __str__(self): return "" From f7c6ca4d1ff4c4dbf6a1be7bae14b1e8ceaa13bc Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 22:58:54 -0700 Subject: [PATCH 09/27] feat: double thin box style --- table2ascii/preset_style.py | 1 + table2ascii/table_style.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py index 5c597aa..8291a8c 100644 --- a/table2ascii/preset_style.py +++ b/table2ascii/preset_style.py @@ -31,6 +31,7 @@ class PresetStyle: 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(" ┃ ━ ━━") diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 639b3c2..361c123 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -24,7 +24,7 @@ class TableStyle: .. code-block:: - ╔═════╦═════╦═════╦═════╦═════╗ + ╔═════╦═════╤═════╤═════╤═════╗ ║ # ║ G │ H │ R │ S ║ ╠═════╬═════╪═════╪═════╪═════╣ ║ 1 ║ 30 │ 40 │ 35 │ 30 ║ @@ -32,7 +32,7 @@ class TableStyle: ║ 2 ║ 30 │ 40 │ 35 │ 30 ║ ╠═════╬═════╪═════╪═════╪═════╣ ║ SUM ║ 130 │ 140 │ 135 │ 130 ║ - ╚═════╩═════╩═════╩═════╩═════╝ + ╚═════╩═════╧═════╧═════╧═════╝ In addition to the parts above, W-Z are used for merged cells as follows: @@ -90,7 +90,7 @@ def from_string(cls, string: str) -> "TableStyle": Example:: - TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝ ──") + TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧") Raises: ValueError: If the string is too long From d2002755feed01d156c84536e8f2eeb85c4d6c5b Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Sun, 11 Dec 2022 23:58:34 -0700 Subject: [PATCH 10/27] Add parts for supporting header column merging --- docs/source/styles.rst | 23 ++++++++++++++++ table2ascii/merge.py | 2 ++ table2ascii/preset_style.py | 2 +- table2ascii/table_style.py | 50 +++++++++++++++++++++++++---------- table2ascii/table_to_ascii.py | 2 +- tests/test_merge.py | 23 ++++++++++++++++ 6 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 tests/test_merge.py 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/table2ascii/merge.py b/table2ascii/merge.py index e9e0b6f..9a76059 100644 --- a/table2ascii/merge.py +++ b/table2ascii/merge.py @@ -31,6 +31,8 @@ class Merge(Enum): ║ ║ contents ║ ╚═══╩═══════════╝ \"\"\" + + .. versionadded:: 1.0.0 """ LEFT = 0 diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py index 8291a8c..276de36 100644 --- a/table2ascii/preset_style.py +++ b/table2ascii/preset_style.py @@ -31,7 +31,7 @@ class PresetStyle: double = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣╠═╬═╣╚╩═╝════") double_box = TableStyle.from_string("╔═╦╦╗║║║╠═╬╬╣╠═╬╬╣╚╩╩╝╦╩╦╩") double_compact = TableStyle.from_string("╔═╦═╗║║ ╠═╬═╣ ╚╩═╝ ══") - double_thin_box = TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧") + double_thin_box = TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧╥╨╦╩") double_thin_compact = TableStyle.from_string("╔═╦═╗║║ ╟─╫─╢ ╚╩═╝ ──") minimalist = TableStyle.from_string(" ─── │ ━━━ ─── ── ──━━") borderless = TableStyle.from_string(" ┃ ━ ━━") diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 361c123..86445db 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -19,8 +19,8 @@ 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:: @@ -34,20 +34,33 @@ class TableStyle: ║ SUM ║ 130 │ 140 │ 135 │ 130 ║ ╚═════╩═════╧═════╧═════╧═════╝ - In addition to the parts above, W-Z are used for merged cells as follows: + 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:: - +-----+-----+---+---+-----+ - | Merge | # | G | S | - +====[Y]====+==[Z]==+=====+ - | 1 | 100 | Long | 150 | - +----[X]----+--[W]--+-----+ - | Bonus | 5 | 5 | 150 | - +====[Y]====+==[Z]==+=====+ - | SUM | 100 | 200 | 300 | - +-----+-----+-------+-----+ - + ╔══════════════╤══════╤══════╗ + ║ 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 ``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 @@ -77,11 +90,20 @@ class TableStyle: 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 a :class:`ValueError` will be raised if it is too long. + Args: string: The string to create the TableStyle from @@ -90,7 +112,7 @@ def from_string(cls, string: str) -> "TableStyle": Example:: - TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧") + TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧╥╨╦╩") Raises: ValueError: If the string is too long diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 3d22810..336df2b 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -532,7 +532,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`. Returns: The generated ASCII table diff --git a/tests/test_merge.py b/tests/test_merge.py new file mode 100644 index 0000000..f32817b --- /dev/null +++ b/tests/test_merge.py @@ -0,0 +1,23 @@ +import pytest + +from table2ascii import Alignment, Merge, PresetStyle, table2ascii as t2a + + +def test_merge(): + text = t2a( + header=["#", "G", "H", "Other", Merge.LEFT], + body=[["1", "60", "40", "45", "80"], ["2", "30", "20", "90", "50"]], + footer=["SUM", "Merged", Merge.LEFT, "135", "130"], + first_col_heading=True, + ) + expected = ( + "╔═════╦═════════════════════╗\n" + "║ # ║ G H Other ║\n" + "╟─────╫─────────────────────╢\n" + "║ 1 ║ 60 40 45 80 ║\n" + "║ 2 ║ 30 20 90 50 ║\n" + "╟─────╫─────────────────────╢\n" + "║ SUM ║ Merged 135 130 ║\n" + "╚═════╩═════════════════════╝" + ) + assert text == expected From 0687a91a70381855e59bdc5073f45805e7033caf Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 00:10:22 -0700 Subject: [PATCH 11/27] Update test_merge.py --- tests/test_merge.py | 62 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/tests/test_merge.py b/tests/test_merge.py index f32817b..5775409 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -3,21 +3,57 @@ from table2ascii import Alignment, Merge, PresetStyle, table2ascii as t2a -def test_merge(): +def test_merge_no_header_column(): text = t2a( - header=["#", "G", "H", "Other", Merge.LEFT], - body=[["1", "60", "40", "45", "80"], ["2", "30", "20", "90", "50"]], - footer=["SUM", "Merged", Merge.LEFT, "135", "130"], - first_col_heading=True, + 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 H Other ║\n" - "╟─────╫─────────────────────╢\n" - "║ 1 ║ 60 40 45 80 ║\n" - "║ 2 ║ 30 20 90 50 ║\n" - "╟─────╫─────────────────────╢\n" - "║ SUM ║ Merged 135 130 ║\n" - "╚═════╩═════════════════════╝" + "╔═════╤═════╤═══════╤═════╗\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 merge 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 merge ║\n" + "║ │ and wraps to multiple lines ║\n" + "╚═════════════╧═════════════════════════════════╝" ) assert text == expected From 8c3a3c55cc38ab660036f440fe583bde4f9502cb Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 00:31:21 -0700 Subject: [PATCH 12/27] Support for merging header columns --- table2ascii/table_to_ascii.py | 75 +++++++++++++++++++++++------------ tests/test_merge.py | 28 +++++++++++++ 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 336df2b..49d1ed6 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -202,10 +202,12 @@ def __row_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], - top_tee: str | None = None, - bottom_tee: str | None = None, 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 @@ -227,10 +229,12 @@ def __row_to_ascii( column_separator, right_edge, filler, - top_tee, - bottom_tee, previous_content_row, next_content_row, + top_tee, + bottom_tee, + heading_col_top_tee, + heading_col_bottom_tee, ) # don't use separation row if it's only space if isinstance(filler, str) and output.strip() == "": @@ -245,10 +249,12 @@ def __line_in_row_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], - top_tee: str | None = None, - bottom_tee: str | None = None, 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 @@ -265,10 +271,12 @@ def __line_in_row_to_ascii( column_separator, right_edge, filler, - top_tee, - bottom_tee, previous_content_row, next_content_row, + top_tee, + bottom_tee, + heading_col_top_tee, + heading_col_bottom_tee, ) output += "\n" return output @@ -281,10 +289,12 @@ def __line_in_cell_column_to_ascii( column_separator: str, right_edge: str, filler: str | list[SupportsStr], - top_tee: str | None = None, - bottom_tee: str | None = None, 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 @@ -320,27 +330,32 @@ def __line_in_cell_column_to_ascii( ) # column separator sep = column_separator - # if this is cell contents and the next column is Merge.LEFT, don't add a separator - if next_value is Merge.LEFT: - sep = "" # handle separators between rows when previous or next row is a merged cell - elif isinstance(filler, str): - empty = (Merge.LEFT, None) # values indicating a merged cell or end of the table + empty = (Merge.LEFT, None) # values indicating a merged cell or end of the table + if isinstance(filler, str): 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 prev_row_next_value in empty and next_row_next_value in empty: sep = filler - # use column heading if first column option is specified - if col_index == 0 and self.__first_col_heading: - sep = heading_col_sep - # use column heading if last column option is specified - elif col_index == self.__columns - 2 and self.__last_col_heading: + # 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 isinstance(filler, str): + 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: 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( @@ -380,9 +395,11 @@ def __top_edge_to_ascii(self) -> str: 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.col_row_bottom_tee, - next_content_row=first_row, + heading_col_top_tee=self.__style.heading_col_top_tee, + heading_col_bottom_tee=self.__style.heading_col_bottom_tee, ) def __bottom_edge_to_ascii(self) -> str: @@ -399,9 +416,11 @@ def __bottom_edge_to_ascii(self) -> str: 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.col_row_top_tee, bottom_tee=self.__style.col_row_bottom_tee, - previous_content_row=last_row, + heading_col_top_tee=self.__style.heading_col_top_tee, + heading_col_bottom_tee=self.__style.heading_col_bottom_tee, ) def __content_row_to_ascii(self, row: list[SupportsStr]) -> str: @@ -434,10 +453,12 @@ def __heading_sep_to_ascii( column_separator=self.__style.heading_row_cross, right_edge=self.__style.heading_row_right_tee, filler=self.__style.heading_row_sep, - top_tee=self.__style.heading_row_top_tee, - bottom_tee=self.__style.heading_row_bottom_tee, 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: @@ -456,10 +477,12 @@ def __body_to_ascii(self, body: list[list[SupportsStr]]) -> str: column_separator=self.__style.col_row_cross, right_edge=self.__style.row_right_tee, filler=self.__style.row_sep, - top_tee=self.__style.col_row_top_tee, - bottom_tee=self.__style.col_row_bottom_tee, 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, ) # content row output += self.__content_row_to_ascii(row) diff --git a/tests/test_merge.py b/tests/test_merge.py index 5775409..6499c44 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -30,6 +30,34 @@ def test_merge_no_header_column(): 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"], From 3c965d46ec22fc1083b468ec4563f4a139c6cd8f Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 00:48:19 -0700 Subject: [PATCH 13/27] refactor --- table2ascii/table_to_ascii.py | 47 ++++++++++++++--------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 49d1ed6..8a0e092 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -313,43 +313,34 @@ def __line_in_cell_column_to_ascii( ) output += col_content # check for merged cells - next_value = ( - filler[col_index + 1] - if not isinstance(filler, str) and col_index < self.__columns - 1 - else None - ) - prev_row_next_value = ( - previous_content_row[col_index + 1] - if previous_content_row is not None and col_index < self.__columns - 1 - else None - ) - next_row_next_value = ( - next_content_row[col_index + 1] - if next_content_row is not None and col_index < self.__columns - 1 - else None - ) + 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 # handle separators between rows when previous or next row is a merged cell - empty = (Merge.LEFT, None) # values indicating a merged cell or end of the table - if isinstance(filler, str): - 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 prev_row_next_value in empty and next_row_next_value in empty: - sep = filler + 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 isinstance(filler, str): - 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 + 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: sep = right_edge From 5efcb0d59a20f21588b428bb11a6cbf91d4a9381 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 07:48:28 +0000 Subject: [PATCH 14/27] style: auto fixes from pre-commit hooks --- table2ascii/table_to_ascii.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 8a0e092..eabce3a 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -316,7 +316,9 @@ def __line_in_cell_column_to_ascii( 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 + 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 From 3935a8f28f0b856df425b4846fd4755edcd51854 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 01:07:27 -0700 Subject: [PATCH 15/27] top and bottom edge tee fixes --- table2ascii/table_to_ascii.py | 61 ++++++++++++++++++----------------- tests/test_merge.py | 33 +++++++++++++++++-- 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index eabce3a..965353a 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -223,18 +223,18 @@ def __row_to_ascii( # 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, - previous_content_row, - next_content_row, - top_tee, - bottom_tee, - heading_col_top_tee, - heading_col_bottom_tee, + 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() == "": @@ -265,18 +265,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, - previous_content_row, - next_content_row, - top_tee, - bottom_tee, - heading_col_top_tee, - heading_col_bottom_tee, + 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 @@ -308,7 +308,10 @@ def __line_in_cell_column_to_ascii( 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, col_index, column_separator, filler + line_index=line_index, + col_index=col_index, + column_separator=column_separator, + filler=filler, ) ) output += col_content @@ -390,9 +393,9 @@ def __top_edge_to_ascii(self) -> str: filler=self.__style.top_and_bottom_edge, next_content_row=first_row, top_tee=self.__style.col_row_top_tee, - bottom_tee=self.__style.col_row_bottom_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.heading_col_bottom_tee, + heading_col_bottom_tee=self.__style.top_and_bottom_edge, ) def __bottom_edge_to_ascii(self) -> str: @@ -410,9 +413,9 @@ def __bottom_edge_to_ascii(self) -> str: right_edge=self.__style.bottom_right_corner, filler=self.__style.top_and_bottom_edge, previous_content_row=last_row, - top_tee=self.__style.col_row_top_tee, + top_tee=self.__style.top_and_bottom_edge, bottom_tee=self.__style.col_row_bottom_tee, - heading_col_top_tee=self.__style.heading_col_top_tee, + heading_col_top_tee=self.__style.top_and_bottom_edge, heading_col_bottom_tee=self.__style.heading_col_bottom_tee, ) diff --git a/tests/test_merge.py b/tests/test_merge.py index 6499c44..4343334 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -1,8 +1,37 @@ -import pytest - 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"], From 9d96c704276f0333dc35443425fdfcbe3563277f Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 01:26:02 -0700 Subject: [PATCH 16/27] Update preset_style.py --- table2ascii/preset_style.py | 60 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py index 276de36..13cb7a6 100644 --- a/table2ascii/preset_style.py +++ b/table2ascii/preset_style.py @@ -16,34 +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("╔═╦═╗║║ ╠═╬═╣ ╚╩═╝ ══") + 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(" ").set(left_and_right_edge="") + 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="") From b7c5c61417ce01c210cf22b23f388cb0457b4923 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 01:31:40 -0700 Subject: [PATCH 17/27] Add compact test --- tests/test_merge.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_merge.py b/tests/test_merge.py index 4343334..0a567dd 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -114,3 +114,32 @@ def test_merge_line_wrap(): "╚═════════════╧═════════════════════════════════╝" ) 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], + ], + column_widths=[4, 4, None, None], + footer=["Footer", Merge.LEFT, "A", "B"], + 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 From c59ee9f2aec6433ce2fd516616e65ccc5c6ca903 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 02:05:01 -0700 Subject: [PATCH 18/27] Update documentation --- README.md | 34 +++++++++++++++++++++++++++ docs/source/api.rst | 7 ++++++ docs/source/usage.rst | 47 ++++++++++++++++++++++++++++++++++---- table2ascii/merge.py | 26 +++++++++++---------- table2ascii/table_style.py | 9 ++++---- tests/test_merge.py | 4 ++-- 6 files changed, 104 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d21fb58..241b55c 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 280bd18..dbb2711 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -3,11 +3,14 @@ API Reference ============= +.. _table2ascii: + table2ascii ~~~~~~~~~~~ .. autofunction:: table2ascii +.. _Alignment: Alignment ~~~~~~~~~ @@ -15,12 +18,16 @@ Alignment .. autoenum:: Alignment :members: +.. _Merge: + Merge ~~~~~ .. autoenum:: Merge :members: +.. _PresetStyle: + PresetStyle ~~~~~~~~~~~ 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/merge.py b/table2ascii/merge.py index 9a76059..3e8888b 100644 --- a/table2ascii/merge.py +++ b/table2ascii/merge.py @@ -8,28 +8,30 @@ class Merge(Enum): with the cell to its left. In the case that the contents of the merged cell are longer than the - contents of the unmerged cells in the rows above and below, the merged - cell will be wrapped onto multiple lines. + 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( - body=[ - ["A", "B", "C", "D"], - ["E", "Long cell contents", Merge.LEFT, Merge.LEFT], - ], + 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, ) \"\"\" - ╔═══╦═══╦═══╦═══╗ - ║ A ║ B ║ C ║ D ║ - ╠═══╬═══╩═══╩═══╣ - ║ E ║ Long cell ║ - ║ ║ contents ║ - ╚═══╩═══════════╝ + ╔═════════════╦═══════╦══════════╦═══════╗ + ║ Name ║ Price ║ Category ║ Stock ║ + ╠═════════════╬═══════╬══════════╩═══════╣ + ║ Milk ║ $2.99 ║ N/A ║ + ╠═════════════╬═══════╩══════════════════╣ + ║ Description ║ Milk is a nutritious ║ + ║ ║ beverage ║ + ╚═════════════╩══════════════════════════╝ \"\"\" .. versionadded:: 1.0.0 diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 86445db..4f64b2e 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -5,7 +5,7 @@ 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:: @@ -58,9 +58,10 @@ class TableStyle: .. versionchanged:: 1.0.0 - Added fields for ``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`` + 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 diff --git a/tests/test_merge.py b/tests/test_merge.py index 0a567dd..ea8edb6 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -95,7 +95,7 @@ def test_merge_line_wrap(): ], footer=[ "Description", - "Long cell value that is merge and wraps to multiple lines", + "Long cell value that is merged and wraps to multiple lines", Merge.LEFT, Merge.LEFT, Merge.LEFT, @@ -109,7 +109,7 @@ def test_merge_line_wrap(): "╠═════════════╪═══════╪══════════╪═══════╪══════╣\n" "║ test │ 443 │ test │ 67 │ test ║\n" "╠═════════════╪═══════╧══════════╧═══════╧══════╣\n" - "║ Description │ Long cell value that is merge ║\n" + "║ Description │ Long cell value that is merged ║\n" "║ │ and wraps to multiple lines ║\n" "╚═════════════╧═════════════════════════════════╝" ) From 4d5e4eeca509f6890e3db6d23f2ec534f2b400f6 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 02:08:21 -0700 Subject: [PATCH 19/27] Remove unused references --- docs/source/api.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index dbb2711..e291182 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -3,15 +3,11 @@ API Reference ============= -.. _table2ascii: - table2ascii ~~~~~~~~~~~ .. autofunction:: table2ascii -.. _Alignment: - Alignment ~~~~~~~~~ @@ -26,8 +22,6 @@ Merge .. autoenum:: Merge :members: -.. _PresetStyle: - PresetStyle ~~~~~~~~~~~ From 5259a84535ed39b0c521196e68ad5b8d2ff4d951 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 02:25:18 -0700 Subject: [PATCH 20/27] Update docstring --- table2ascii/table_to_ascii.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 965353a..e7eb240 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -420,10 +420,10 @@ def __bottom_edge_to_ascii(self) -> str: ) def __content_row_to_ascii(self, row: list[SupportsStr]) -> str: - """Assembles the header or footer row line of the ascii table + """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, From 12cd4a5501f5fb95f4d3e424ec9db3e3274de0d9 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:19:31 -0700 Subject: [PATCH 21/27] Custom tablestyle warnings and exceptions --- docs/source/api.rst | 7 ++++++ table2ascii/exceptions.py | 47 ++++++++++++++++++++++++++++++++++++++ table2ascii/table_style.py | 10 ++++---- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index e291182..6b16e63 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -58,3 +58,10 @@ Exceptions .. autoexception:: table2ascii.exceptions.ColumnWidthTooSmallError .. autoexception:: table2ascii.exceptions.InvalidAlignmentError + +.. autoexception:: table2ascii.exceptions.TableStyleTooLongError + +Warnings +~~~~~~~~ + +.. autoclass:: table2ascii.exceptions.TableStyleTooShortWarning diff --git a/table2ascii/exceptions.py b/table2ascii/exceptions.py index efdf177..cdd7c9d 100644 --- a/table2ascii/exceptions.py +++ b/table2ascii/exceptions.py @@ -189,3 +189,50 @@ 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`. + """ + + def __init__(self, string: str, min_characters: int): + self.string = string + self.min_characters = min_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.min_characters} characters. " + f"Missing characters will be replaced with spaces." + ) diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 4f64b2e..8977895 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -1,4 +1,7 @@ from dataclasses import dataclass +import warnings + +from .exceptions import TableStyleTooShortWarning, TableStyleTooLongError @dataclass @@ -121,12 +124,11 @@ def from_string(cls, string: str) -> "TableStyle": num_params = len(cls.__dataclass_fields__) # if the string is too long, raise an error if len(string) > num_params: - raise ValueError( - f"Too many characters in string. Expected {num_params} but got {len(string)}." - ) - # if the string is too short, pad it with spaces + 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": From 1dae78369b4db706a60cb3cfd4a02d890437c957 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:19:50 -0700 Subject: [PATCH 22/27] Clean up and add to styles tests --- tests/test_styles.py | 664 ++++--------------------------------------- 1 file changed, 54 insertions(+), 610 deletions(-) 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" From 4765a3bf561bf952e069e27f6f0f8950dff78425 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:22:04 -0700 Subject: [PATCH 23/27] Change min to max_characters --- table2ascii/exceptions.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/table2ascii/exceptions.py b/table2ascii/exceptions.py index cdd7c9d..4af1a26 100644 --- a/table2ascii/exceptions.py +++ b/table2ascii/exceptions.py @@ -223,16 +223,20 @@ class TableStyleTooShortWarning(UserWarning): 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, min_characters: int): + def __init__(self, string: str, max_characters: int): self.string = string - self.min_characters = min_characters + 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.min_characters} characters. " + f"found, but table styles can accept {self.max_characters} characters. " f"Missing characters will be replaced with spaces." ) From 1c77e2b25b45f64e12cab4f29b1c71deea721520 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:35:41 -0700 Subject: [PATCH 24/27] fix rows beginning with Merge.LEFT --- table2ascii/table_to_ascii.py | 14 ++++++++++++++ tests/test_merge.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index e7eb240..fdb174d 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -70,6 +70,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. @@ -141,6 +144,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 diff --git a/tests/test_merge.py b/tests/test_merge.py index ea8edb6..3555ce9 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -125,8 +125,8 @@ def test_merge_compact(): ["F", "G", "H", "I"], ["J", "K", "L", Merge.LEFT], ], - column_widths=[4, 4, None, None], footer=["Footer", Merge.LEFT, "A", "B"], + column_widths=[4, 4, None, None], style=PresetStyle.double_thin_compact, first_col_heading=True, ) @@ -143,3 +143,33 @@ def test_merge_compact(): "╚═════════════════╝" ) 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 From f5d00bc02b982726c55f86c4929d1774e813c68f Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:39:10 -0700 Subject: [PATCH 25/27] Update docstring --- table2ascii/table_style.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index 8977895..ff6d7a8 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -105,8 +105,8 @@ def from_string(cls, string: str) -> "TableStyle": .. versionchanged:: 1.0.0 - The string will be padded on the right with spaces if it is too - short and a :class:`ValueError` will be raised if it is too long. + The string will be padded on the right with spaces if it is too short + and :class:`TableStyleTooLongError` will be raised if it is too long. Args: string: The string to create the TableStyle from @@ -119,7 +119,7 @@ def from_string(cls, string: str) -> "TableStyle": TableStyle.from_string("╔═╦╤╗║║│╠═╬╪╣╟─╫┼╢╚╩╧╝┬┴╤╧╥╨╦╩") Raises: - ValueError: If the string is too long + TableStyleTooLongError: If the string is too long """ num_params = len(cls.__dataclass_fields__) # if the string is too long, raise an error From a0c3b3e5d1547fc262b36b1c415038a8e5df2036 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:45:01 -0700 Subject: [PATCH 26/27] Fix docs link --- table2ascii/table_style.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/table2ascii/table_style.py b/table2ascii/table_style.py index ff6d7a8..620eb77 100644 --- a/table2ascii/table_style.py +++ b/table2ascii/table_style.py @@ -106,7 +106,8 @@ def from_string(cls, string: str) -> "TableStyle": .. versionchanged:: 1.0.0 The string will be padded on the right with spaces if it is too short - and :class:`TableStyleTooLongError` will be raised if it is too long. + and :class:`~table2ascii.exceptions.TableStyleTooLongError` will be + raised if it is too long. Args: string: The string to create the TableStyle from From 5080127d2ad8cc2edd8de87cc15f58eab428c3a8 Mon Sep 17 00:00:00 2001 From: Jonah Lawrence Date: Mon, 12 Dec 2022 11:49:22 -0700 Subject: [PATCH 27/27] refactor: readability of plain --- table2ascii/preset_style.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/table2ascii/preset_style.py b/table2ascii/preset_style.py index 13cb7a6..d82a0a9 100644 --- a/table2ascii/preset_style.py +++ b/table2ascii/preset_style.py @@ -46,4 +46,4 @@ class PresetStyle: 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="") + plain = TableStyle.from_string(" " * 30).set(left_and_right_edge="")