From a2836610a9dda6b0257cef9c5ce8207608f6c587 Mon Sep 17 00:00:00 2001 From: Robpol86 Date: Mon, 30 May 2016 16:29:53 -0700 Subject: [PATCH] Migrated to granular character control. More CHAR class variables so that in the future heading/footing borders can be distinct. Fixes https://github.com/Robpol86/terminaltables/issues/26 --- README.rst | 4 + terminaltables/base_table.py | 112 +++++++++++----- terminaltables/github_table.py | 26 ++-- terminaltables/other_tables.py | 120 +++++++++++++----- tests/test_base_table/test_gen_row_lines.py | 52 +++++--- tests/test_base_table/test_gen_table.py | 2 +- .../test_base_table/test_horizontal_border.py | 86 ++++++++++--- 7 files changed, 291 insertions(+), 111 deletions(-) diff --git a/README.rst b/README.rst index 6c5e57f..c647135 100644 --- a/README.rst +++ b/README.rst @@ -176,6 +176,9 @@ Added * Support for https://pypi.python.org/pypi/termcolor * Support for RTL characters (Arabic and Hebrew). +Changed + * Refactored again, but this time entire project including tests. + Removed * ``padded_table_data`` property and ``join_row()``. Moving away from repeated string joining/splitting. @@ -183,6 +186,7 @@ Fixed * ``set_terminal_title()`` Unicode handling on Windows. * https://github.com/Robpol86/terminaltables/issues/20 * https://github.com/Robpol86/terminaltables/issues/23 + * https://github.com/Robpol86/terminaltables/issues/26 2.1.0 - 2015-11-02 ------------------ diff --git a/terminaltables/base_table.py b/terminaltables/base_table.py index d2befc7..3f7e3cc 100644 --- a/terminaltables/base_table.py +++ b/terminaltables/base_table.py @@ -19,17 +19,35 @@ class BaseTable(object): :ivar int padding_right: Number of spaces to pad on the right side of every cell. """ - CHAR_CORNER_LOWER_LEFT = '+' - CHAR_CORNER_LOWER_RIGHT = '+' - CHAR_CORNER_UPPER_LEFT = '+' - CHAR_CORNER_UPPER_RIGHT = '+' - CHAR_HORIZONTAL = '-' - CHAR_INTERSECT_BOTTOM = '+' - CHAR_INTERSECT_CENTER = '+' - CHAR_INTERSECT_LEFT = '+' - CHAR_INTERSECT_RIGHT = '+' - CHAR_INTERSECT_TOP = '+' - CHAR_VERTICAL = '|' + CHAR_F_INNER_HORIZONTAL = '-' + CHAR_F_INNER_INTERSECT = '+' + CHAR_F_INNER_VERTICAL = '|' + CHAR_F_OUTER_LEFT_INTERSECT = '+' + CHAR_F_OUTER_LEFT_VERTICAL = '|' + CHAR_F_OUTER_RIGHT_INTERSECT = '+' + CHAR_F_OUTER_RIGHT_VERTICAL = '|' + CHAR_H_INNER_HORIZONTAL = '-' + CHAR_H_INNER_INTERSECT = '+' + CHAR_H_INNER_VERTICAL = '|' + CHAR_H_OUTER_LEFT_INTERSECT = '+' + CHAR_H_OUTER_LEFT_VERTICAL = '|' + CHAR_H_OUTER_RIGHT_INTERSECT = '+' + CHAR_H_OUTER_RIGHT_VERTICAL = '|' + CHAR_INNER_HORIZONTAL = '-' + CHAR_INNER_INTERSECT = '+' + CHAR_INNER_VERTICAL = '|' + CHAR_OUTER_BOTTOM_HORIZONTAL = '-' + CHAR_OUTER_BOTTOM_INTERSECT = '+' + CHAR_OUTER_BOTTOM_LEFT = '+' + CHAR_OUTER_BOTTOM_RIGHT = '+' + CHAR_OUTER_LEFT_INTERSECT = '+' + CHAR_OUTER_LEFT_VERTICAL = '|' + CHAR_OUTER_RIGHT_INTERSECT = '+' + CHAR_OUTER_RIGHT_VERTICAL = '|' + CHAR_OUTER_TOP_HORIZONTAL = '-' + CHAR_OUTER_TOP_INTERSECT = '+' + CHAR_OUTER_TOP_LEFT = '+' + CHAR_OUTER_TOP_RIGHT = '+' def __init__(self, table_data, title=None): """Constructor. @@ -60,23 +78,38 @@ def horizontal_border(self, style, outer_widths): :rtype: tuple """ if style == 'top': - left = self.CHAR_CORNER_UPPER_LEFT - center = self.CHAR_INTERSECT_TOP if self.inner_column_border else '' - right = self.CHAR_CORNER_UPPER_RIGHT + horizontal = self.CHAR_OUTER_TOP_HORIZONTAL + left = self.CHAR_OUTER_TOP_LEFT + intersect = self.CHAR_OUTER_TOP_INTERSECT if self.inner_column_border else '' + right = self.CHAR_OUTER_TOP_RIGHT title = self.title elif style == 'bottom': - left = self.CHAR_CORNER_LOWER_LEFT - center = self.CHAR_INTERSECT_BOTTOM if self.inner_column_border else '' - right = self.CHAR_CORNER_LOWER_RIGHT + horizontal = self.CHAR_OUTER_BOTTOM_HORIZONTAL + left = self.CHAR_OUTER_BOTTOM_LEFT + intersect = self.CHAR_OUTER_BOTTOM_INTERSECT if self.inner_column_border else '' + right = self.CHAR_OUTER_BOTTOM_RIGHT + title = None + elif style == 'heading': + horizontal = self.CHAR_H_INNER_HORIZONTAL + left = self.CHAR_H_OUTER_LEFT_INTERSECT if self.outer_border else '' + intersect = self.CHAR_H_INNER_INTERSECT if self.inner_column_border else '' + right = self.CHAR_H_OUTER_RIGHT_INTERSECT if self.outer_border else '' + title = None + elif style == 'footing': + horizontal = self.CHAR_F_INNER_HORIZONTAL + left = self.CHAR_F_OUTER_LEFT_INTERSECT if self.outer_border else '' + intersect = self.CHAR_F_INNER_INTERSECT if self.inner_column_border else '' + right = self.CHAR_F_OUTER_RIGHT_INTERSECT if self.outer_border else '' title = None else: - left = self.CHAR_INTERSECT_LEFT if self.outer_border else '' - center = self.CHAR_INTERSECT_CENTER if self.inner_column_border else '' - right = self.CHAR_INTERSECT_RIGHT if self.outer_border else '' + horizontal = self.CHAR_INNER_HORIZONTAL + left = self.CHAR_OUTER_LEFT_INTERSECT if self.outer_border else '' + intersect = self.CHAR_INNER_INTERSECT if self.inner_column_border else '' + right = self.CHAR_OUTER_RIGHT_INTERSECT if self.outer_border else '' title = None - return build_border(outer_widths, self.CHAR_HORIZONTAL, left, center, right, title) + return build_border(outer_widths, horizontal, left, intersect, right, title) - def gen_row_lines(self, row, inner_widths, height): + def gen_row_lines(self, row, style, inner_widths, height): r"""Combine cells in row and group them into lines with vertical borders. Caller is expected to pass yielded lines to ''.join() to combine them into a printable line. Caller must append @@ -98,6 +131,7 @@ def gen_row_lines(self, row, inner_widths, height): ] :param iter row: One row in the table. List of cells. + :param str style: Type of border characters to use. :param iter inner_widths: List of widths (no padding) for each column. :param int height: Inner height (no padding) (number of lines) to expand row to. @@ -116,16 +150,22 @@ def gen_row_lines(self, row, inner_widths, height): padding = (self.padding_left, self.padding_right, 0, 0) cells_in_row.append(align_and_pad_cell(cell, align, inner_dimensions, padding)) - # Combine cells and borders. - lines = build_row( - cells_in_row, - self.CHAR_VERTICAL if self.outer_border else '', - self.CHAR_VERTICAL if self.inner_column_border else '', - self.CHAR_VERTICAL if self.outer_border else '' - ) + # Determine border characters. + if style == 'heading': + left = self.CHAR_H_OUTER_LEFT_VERTICAL if self.outer_border else '' + center = self.CHAR_H_INNER_VERTICAL if self.inner_column_border else '' + right = self.CHAR_H_OUTER_RIGHT_VERTICAL if self.outer_border else '' + elif style == 'footing': + left = self.CHAR_F_OUTER_LEFT_VERTICAL if self.outer_border else '' + center = self.CHAR_F_INNER_VERTICAL if self.inner_column_border else '' + right = self.CHAR_F_OUTER_RIGHT_VERTICAL if self.outer_border else '' + else: + left = self.CHAR_OUTER_LEFT_VERTICAL if self.outer_border else '' + center = self.CHAR_INNER_VERTICAL if self.inner_column_border else '' + right = self.CHAR_OUTER_RIGHT_VERTICAL if self.outer_border else '' # Yield each line. - for line in lines: + for line in build_row(cells_in_row, left, center, right): yield line def gen_table(self, inner_widths, inner_heights, outer_widths): @@ -145,15 +185,21 @@ def gen_table(self, inner_widths, inner_heights, outer_widths): last_row_index, before_last_row_index = row_count - 1, row_count - 2 for i, row in enumerate(self.table_data): # Yield the row line by line (e.g. multi-line rows). - for line in self.gen_row_lines(row, inner_widths, inner_heights[i]): + if self.inner_heading_row_border and i == 0: + style = 'heading' + elif self.inner_footing_row_border and i == last_row_index: + style = 'footing' + else: + style = 'row' + for line in self.gen_row_lines(row, style, inner_widths, inner_heights[i]): yield line # If this is the last row then break. No separator needed. if i == last_row_index: break - # Yield header separator. + # Yield heading separator. if self.inner_heading_row_border and i == 0: yield self.horizontal_border('heading', outer_widths) - # Yield footer separator. + # Yield footing separator. elif self.inner_footing_row_border and i == before_last_row_index: yield self.horizontal_border('footing', outer_widths) # Yield row separator. diff --git a/terminaltables/github_table.py b/terminaltables/github_table.py index 0459479..7eb1be7 100644 --- a/terminaltables/github_table.py +++ b/terminaltables/github_table.py @@ -8,9 +8,10 @@ class GithubFlavoredMarkdownTable(AsciiTable): """Github flavored markdown table. https://help.github.com/articles/github-flavored-markdown/#tables - """ - CHAR_HORIZONTAL = '-' + :ivar iter table_data: List (empty or list of lists of strings) representing the table. + :ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center. + """ def __init__(self, table_data): """Constructor. @@ -21,7 +22,7 @@ def __init__(self, table_data): super(GithubFlavoredMarkdownTable, self).__init__(table_data) def horizontal_border(self, _, outer_widths): - """Handle the GitHub header border. + """Handle the GitHub heading border. E.g.: |:---|:---:|---:|----| @@ -32,20 +33,25 @@ def horizontal_border(self, _, outer_widths): :return: Prepared border strings in a generator. :rtype: iter """ + horizontal = str(self.CHAR_INNER_HORIZONTAL) + left = self.CHAR_OUTER_LEFT_VERTICAL + intersect = self.CHAR_INNER_VERTICAL + right = self.CHAR_OUTER_RIGHT_VERTICAL + columns = list() for i, width in enumerate(outer_widths): justify = self.justify_columns.get(i) width = max(3, width) # Width should be at least 3 so justification can be applied. if justify == 'left': - columns.append(':' + self.CHAR_HORIZONTAL * (width - 1)) + columns.append(':' + horizontal * (width - 1)) elif justify == 'right': - columns.append(self.CHAR_HORIZONTAL * (width - 1) + ':') + columns.append(horizontal * (width - 1) + ':') elif justify == 'center': - columns.append(':' + self.CHAR_HORIZONTAL * (width - 2) + ':') + columns.append(':' + horizontal * (width - 2) + ':') else: - columns.append(self.CHAR_HORIZONTAL * width) + columns.append(horizontal * width) - return combine(columns, self.CHAR_VERTICAL, self.CHAR_VERTICAL, self.CHAR_VERTICAL) + return combine(columns, left, intersect, right) def gen_table(self, inner_widths, inner_heights, outer_widths): """Combine everything and yield every line of the entire table with borders. @@ -57,8 +63,8 @@ def gen_table(self, inner_widths, inner_heights, outer_widths): """ for i, row in enumerate(self.table_data): # Yield the row line by line (e.g. multi-line rows). - for line in self.gen_row_lines(row, inner_widths, inner_heights[i]): + for line in self.gen_row_lines(row, 'row', inner_widths, inner_heights[i]): yield line - # Yield header separator. + # Yield heading separator. if i == 0: yield self.horizontal_border(None, outer_widths) diff --git a/terminaltables/other_tables.py b/terminaltables/other_tables.py index 4a9d0a8..df435ca 100644 --- a/terminaltables/other_tables.py +++ b/terminaltables/other_tables.py @@ -10,17 +10,35 @@ class UnixTable(AsciiTable): Similar to the tables shown on PC BIOS boot messages, but not double-lined. """ - CHAR_CORNER_LOWER_LEFT = '\033(0\x6d\033(B' - CHAR_CORNER_LOWER_RIGHT = '\033(0\x6a\033(B' - CHAR_CORNER_UPPER_LEFT = '\033(0\x6c\033(B' - CHAR_CORNER_UPPER_RIGHT = '\033(0\x6b\033(B' - CHAR_HORIZONTAL = '\033(0\x71\033(B' - CHAR_INTERSECT_BOTTOM = '\033(0\x76\033(B' - CHAR_INTERSECT_CENTER = '\033(0\x6e\033(B' - CHAR_INTERSECT_LEFT = '\033(0\x74\033(B' - CHAR_INTERSECT_RIGHT = '\033(0\x75\033(B' - CHAR_INTERSECT_TOP = '\033(0\x77\033(B' - CHAR_VERTICAL = '\033(0\x78\033(B' + CHAR_F_INNER_HORIZONTAL = '\033(0\x71\033(B' + CHAR_F_INNER_INTERSECT = '\033(0\x6e\033(B' + CHAR_F_INNER_VERTICAL = '\033(0\x78\033(B' + CHAR_F_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B' + CHAR_F_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B' + CHAR_F_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B' + CHAR_F_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B' + CHAR_H_INNER_HORIZONTAL = '\033(0\x71\033(B' + CHAR_H_INNER_INTERSECT = '\033(0\x6e\033(B' + CHAR_H_INNER_VERTICAL = '\033(0\x78\033(B' + CHAR_H_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B' + CHAR_H_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B' + CHAR_H_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B' + CHAR_H_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B' + CHAR_INNER_HORIZONTAL = '\033(0\x71\033(B' + CHAR_INNER_INTERSECT = '\033(0\x6e\033(B' + CHAR_INNER_VERTICAL = '\033(0\x78\033(B' + CHAR_OUTER_BOTTOM_HORIZONTAL = '\033(0\x71\033(B' + CHAR_OUTER_BOTTOM_INTERSECT = '\033(0\x76\033(B' + CHAR_OUTER_BOTTOM_LEFT = '\033(0\x6d\033(B' + CHAR_OUTER_BOTTOM_RIGHT = '\033(0\x6a\033(B' + CHAR_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B' + CHAR_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B' + CHAR_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B' + CHAR_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B' + CHAR_OUTER_TOP_HORIZONTAL = '\033(0\x71\033(B' + CHAR_OUTER_TOP_INTERSECT = '\033(0\x77\033(B' + CHAR_OUTER_TOP_LEFT = '\033(0\x6c\033(B' + CHAR_OUTER_TOP_RIGHT = '\033(0\x6b\033(B' @property def table(self): @@ -36,33 +54,69 @@ class WindowsTable(AsciiTable): From: http://en.wikipedia.org/wiki/Code_page_437#Characters """ - CHAR_CORNER_LOWER_LEFT = b'\xc0'.decode('ibm437') - CHAR_CORNER_LOWER_RIGHT = b'\xd9'.decode('ibm437') - CHAR_CORNER_UPPER_LEFT = b'\xda'.decode('ibm437') - CHAR_CORNER_UPPER_RIGHT = b'\xbf'.decode('ibm437') - CHAR_HORIZONTAL = b'\xc4'.decode('ibm437') - CHAR_INTERSECT_BOTTOM = b'\xc1'.decode('ibm437') - CHAR_INTERSECT_CENTER = b'\xc5'.decode('ibm437') - CHAR_INTERSECT_LEFT = b'\xc3'.decode('ibm437') - CHAR_INTERSECT_RIGHT = b'\xb4'.decode('ibm437') - CHAR_INTERSECT_TOP = b'\xc2'.decode('ibm437') - CHAR_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_F_INNER_HORIZONTAL = b'\xc4'.decode('ibm437') + CHAR_F_INNER_INTERSECT = b'\xc5'.decode('ibm437') + CHAR_F_INNER_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_F_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437') + CHAR_F_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_F_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437') + CHAR_F_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_H_INNER_HORIZONTAL = b'\xc4'.decode('ibm437') + CHAR_H_INNER_INTERSECT = b'\xc5'.decode('ibm437') + CHAR_H_INNER_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_H_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437') + CHAR_H_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_H_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437') + CHAR_H_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_INNER_HORIZONTAL = b'\xc4'.decode('ibm437') + CHAR_INNER_INTERSECT = b'\xc5'.decode('ibm437') + CHAR_INNER_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_OUTER_BOTTOM_HORIZONTAL = b'\xc4'.decode('ibm437') + CHAR_OUTER_BOTTOM_INTERSECT = b'\xc1'.decode('ibm437') + CHAR_OUTER_BOTTOM_LEFT = b'\xc0'.decode('ibm437') + CHAR_OUTER_BOTTOM_RIGHT = b'\xd9'.decode('ibm437') + CHAR_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437') + CHAR_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437') + CHAR_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437') + CHAR_OUTER_TOP_HORIZONTAL = b'\xc4'.decode('ibm437') + CHAR_OUTER_TOP_INTERSECT = b'\xc2'.decode('ibm437') + CHAR_OUTER_TOP_LEFT = b'\xda'.decode('ibm437') + CHAR_OUTER_TOP_RIGHT = b'\xbf'.decode('ibm437') class WindowsTableDouble(AsciiTable): """Draw a table using box-drawing characters on Windows platforms. This uses Code Page 437. Double-line borders.""" - CHAR_CORNER_LOWER_LEFT = b'\xc8'.decode('ibm437') - CHAR_CORNER_LOWER_RIGHT = b'\xbc'.decode('ibm437') - CHAR_CORNER_UPPER_LEFT = b'\xc9'.decode('ibm437') - CHAR_CORNER_UPPER_RIGHT = b'\xbb'.decode('ibm437') - CHAR_HORIZONTAL = b'\xcd'.decode('ibm437') - CHAR_INTERSECT_BOTTOM = b'\xca'.decode('ibm437') - CHAR_INTERSECT_CENTER = b'\xce'.decode('ibm437') - CHAR_INTERSECT_LEFT = b'\xcc'.decode('ibm437') - CHAR_INTERSECT_RIGHT = b'\xb9'.decode('ibm437') - CHAR_INTERSECT_TOP = b'\xcb'.decode('ibm437') - CHAR_VERTICAL = b'\xba'.decode('ibm437') + CHAR_F_INNER_HORIZONTAL = b'\xcd'.decode('ibm437') + CHAR_F_INNER_INTERSECT = b'\xce'.decode('ibm437') + CHAR_F_INNER_VERTICAL = b'\xba'.decode('ibm437') + CHAR_F_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437') + CHAR_F_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437') + CHAR_F_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437') + CHAR_F_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437') + CHAR_H_INNER_HORIZONTAL = b'\xcd'.decode('ibm437') + CHAR_H_INNER_INTERSECT = b'\xce'.decode('ibm437') + CHAR_H_INNER_VERTICAL = b'\xba'.decode('ibm437') + CHAR_H_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437') + CHAR_H_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437') + CHAR_H_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437') + CHAR_H_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437') + CHAR_INNER_HORIZONTAL = b'\xcd'.decode('ibm437') + CHAR_INNER_INTERSECT = b'\xce'.decode('ibm437') + CHAR_INNER_VERTICAL = b'\xba'.decode('ibm437') + CHAR_OUTER_BOTTOM_HORIZONTAL = b'\xcd'.decode('ibm437') + CHAR_OUTER_BOTTOM_INTERSECT = b'\xca'.decode('ibm437') + CHAR_OUTER_BOTTOM_LEFT = b'\xc8'.decode('ibm437') + CHAR_OUTER_BOTTOM_RIGHT = b'\xbc'.decode('ibm437') + CHAR_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437') + CHAR_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437') + CHAR_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437') + CHAR_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437') + CHAR_OUTER_TOP_HORIZONTAL = b'\xcd'.decode('ibm437') + CHAR_OUTER_TOP_INTERSECT = b'\xcb'.decode('ibm437') + CHAR_OUTER_TOP_LEFT = b'\xc9'.decode('ibm437') + CHAR_OUTER_TOP_RIGHT = b'\xbb'.decode('ibm437') class SingleTable(WindowsTable if IS_WINDOWS else UnixTable): diff --git a/tests/test_base_table/test_gen_row_lines.py b/tests/test_base_table/test_gen_row_lines.py index 91e8de4..0d0f43c 100644 --- a/tests/test_base_table/test_gen_row_lines.py +++ b/tests/test_base_table/test_gen_row_lines.py @@ -1,24 +1,34 @@ """Test method in BaseTable class.""" +import pytest + from terminaltables.base_table import BaseTable -def test_single_line(): - """Test with single-line row.""" +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_single_line(style): + """Test with single-line row. + + :param str style: Passed to method. + """ row = ['Row One Column One', 'Two', 'Three'] table = BaseTable([row]) - actual = [tuple(i) for i in table.gen_row_lines(row, [18, 3, 5], 1)] + actual = [tuple(i) for i in table.gen_row_lines(row, style, [18, 3, 5], 1)] expected = [ ('|', ' Row One Column One ', '|', ' Two ', '|', ' Three ', '|'), ] assert actual == expected -def test_multi_line(): - """Test with multi-line row.""" +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_multi_line(style): + """Test with multi-line row. + + :param str style: Passed to method. + """ row = ['Row One\nColumn One', 'Two', 'Three'] table = BaseTable([row]) - actual = [tuple(i) for i in table.gen_row_lines(row, [10, 3, 5], 2)] + actual = [tuple(i) for i in table.gen_row_lines(row, style, [10, 3, 5], 2)] expected = [ ('|', ' Row One ', '|', ' Two ', '|', ' Three ', '|'), ('|', ' Column One ', '|', ' ', '|', ' ', '|'), @@ -26,15 +36,19 @@ def test_multi_line(): assert actual == expected -def test_no_padding_no_borders(): - """Test without padding or borders.""" +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_no_padding_no_borders(style): + """Test without padding or borders. + + :param str style: Passed to method. + """ row = ['Row One\nColumn One', 'Two', 'Three'] table = BaseTable([row]) table.inner_column_border = False table.outer_border = False table.padding_left = 0 table.padding_right = 0 - actual = [tuple(i) for i in table.gen_row_lines(row, [10, 3, 5], 2)] + actual = [tuple(i) for i in table.gen_row_lines(row, style, [10, 3, 5], 2)] expected = [ ('Row One ', 'Two', 'Three'), ('Column One', ' ', ' '), @@ -42,22 +56,30 @@ def test_no_padding_no_borders(): assert actual == expected -def test_uneven(): - """Test with row missing cells.""" +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_uneven(style): + """Test with row missing cells. + + :param str style: Passed to method. + """ row = ['Row One Column One'] table = BaseTable([row]) - actual = [tuple(i) for i in table.gen_row_lines(row, [18, 3, 5], 1)] + actual = [tuple(i) for i in table.gen_row_lines(row, style, [18, 3, 5], 1)] expected = [ ('|', ' Row One Column One ', '|', ' ', '|', ' ', '|'), ] assert actual == expected -def test_empty_table(): - """Test empty table.""" +@pytest.mark.parametrize('style', ['heading', 'footing', 'row']) +def test_empty_table(style): + """Test empty table. + + :param str style: Passed to method. + """ row = [] table = BaseTable([row]) - actual = [tuple(i) for i in table.gen_row_lines(row, [], 0)] + actual = [tuple(i) for i in table.gen_row_lines(row, style, [], 0)] expected = [ ('|', '|'), ] diff --git a/tests/test_base_table/test_gen_table.py b/tests/test_base_table/test_gen_table.py index 82c8479..54d5fe1 100644 --- a/tests/test_base_table/test_gen_table.py +++ b/tests/test_base_table/test_gen_table.py @@ -11,7 +11,7 @@ @pytest.mark.parametrize('inner_footing_row_border', [True, False]) @pytest.mark.parametrize('inner_row_border', [True, False]) def test_inner_row_borders(inner_heading_row_border, inner_footing_row_border, inner_row_border): - """Test header/footer/row borders. + """Test heading/footing/row borders. :param bool inner_heading_row_border: Passed to table. :param bool inner_footing_row_border: Passed to table. diff --git a/tests/test_base_table/test_horizontal_border.py b/tests/test_base_table/test_horizontal_border.py index ca434fd..e162261 100644 --- a/tests/test_base_table/test_horizontal_border.py +++ b/tests/test_base_table/test_horizontal_border.py @@ -5,38 +5,86 @@ from terminaltables.base_table import BaseTable from terminaltables.width_and_alignment import max_dimensions +SINGLE_LINE = ( + ('Name', 'Color', 'Type'), + ('Avocado', 'green', 'nut'), + ('Tomato', 'red', 'fruit'), + ('Lettuce', 'green', 'vegetable'), +) + + +@pytest.mark.parametrize('inner_column_border', [True, False]) +@pytest.mark.parametrize('style', ['top', 'bottom']) +def test_top_bottom(inner_column_border, style): + """Test top and bottom borders. + + :param bool inner_column_border: Passed to table class. + :param str style: Passed to method. + """ + table = BaseTable(SINGLE_LINE, 'Example') + table.inner_column_border = inner_column_border + outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2] + + # Determine expected. + if style == 'top' and inner_column_border: + expected = '+Example--+-------+-----------+' + elif style == 'top': + expected = '+Example--------------------+' + elif style == 'bottom' and inner_column_border: + expected = '+---------+-------+-----------+' + else: + expected = '+---------------------------+' + + # Test. + actual = ''.join(table.horizontal_border(style, outer_widths)) + assert actual == expected + @pytest.mark.parametrize('inner_column_border', [True, False]) @pytest.mark.parametrize('outer_border', [True, False]) -@pytest.mark.parametrize('style', ['top', 'bottom', 'row']) -def test(inner_column_border, outer_border, style): - """Test method. +@pytest.mark.parametrize('style', ['heading', 'footing']) +def test_heading_footing(inner_column_border, outer_border, style): + """Test heading and footing borders. :param bool inner_column_border: Passed to table class. :param bool outer_border: Passed to table class. :param str style: Passed to method. """ - table_data = [ - ['Name', 'Color', 'Type'], - ['Avocado', 'green', 'nut'], - ['Tomato', 'red', 'fruit'], - ['Lettuce', 'green', 'vegetable'], - ] - table = BaseTable(table_data, 'Example') + table = BaseTable(SINGLE_LINE) table.inner_column_border = inner_column_border table.outer_border = outer_border outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2] - # Skip illogical. - if outer_border and style in ('top', 'bottom'): - return pytest.skip('Not expected to be called with no outer border.') - # Determine expected. - if style == 'top': - expected = '+Example--+-------+-----------+' if inner_column_border else '+Example--------------------+' - elif style == 'bottom': + if style == 'heading' and outer_border: expected = '+---------+-------+-----------+' if inner_column_border else '+---------------------------+' - elif inner_column_border and outer_border: + elif style == 'heading': + expected = '---------+-------+-----------' if inner_column_border else '---------------------------' + elif style == 'footing' and outer_border: + expected = '+---------+-------+-----------+' if inner_column_border else '+---------------------------+' + else: + expected = '---------+-------+-----------' if inner_column_border else '---------------------------' + + # Test. + actual = ''.join(table.horizontal_border(style, outer_widths)) + assert actual == expected + + +@pytest.mark.parametrize('inner_column_border', [True, False]) +@pytest.mark.parametrize('outer_border', [True, False]) +def test_row(inner_column_border, outer_border): + """Test inner borders. + + :param bool inner_column_border: Passed to table class. + :param bool outer_border: Passed to table class. + """ + table = BaseTable(SINGLE_LINE) + table.inner_column_border = inner_column_border + table.outer_border = outer_border + outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2] + + # Determine expected. + if inner_column_border and outer_border: expected = '+---------+-------+-----------+' elif inner_column_border: expected = '---------+-------+-----------' @@ -46,5 +94,5 @@ def test(inner_column_border, outer_border, style): expected = '---------------------------' # Test. - actual = ''.join(table.horizontal_border(style, outer_widths)) + actual = ''.join(table.horizontal_border('row', outer_widths)) assert actual == expected