From f53fcda543b439bbb7d235b58c146aa1ef0e0ab8 Mon Sep 17 00:00:00 2001 From: Helvio Junior Date: Fri, 10 Mar 2023 02:04:30 -0300 Subject: [PATCH 1/5] Bugfix in tables without linebetweenrows --- tabulate/__init__.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/tabulate/__init__.py b/tabulate/__init__.py index 3b1a1e11..d891d4c0 100644 --- a/tabulate/__init__.py +++ b/tabulate/__init__.py @@ -2376,13 +2376,14 @@ def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline, rowali if fmt.linebelowheader and "linebelowheader" not in hidden: _append_line(lines, padded_widths, colaligns, fmt.linebelowheader) - if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden: + if padded_rows: # initial rows with a line below for row, ralign in zip(padded_rows[:-1], rowaligns): append_row( lines, row, padded_widths, colaligns, fmt.datarow, rowalign=ralign ) - _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows) + if fmt.linebetweenrows and "linebetweenrows" not in hidden: + _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows) # the last row without a line below append_row( lines, @@ -2392,21 +2393,7 @@ def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline, rowali fmt.datarow, rowalign=rowaligns[-1], ) - else: - separating_line = ( - fmt.linebetweenrows - or fmt.linebelowheader - or fmt.linebelow - or fmt.lineabove - or Line("", "", "", "") - ) - for row in padded_rows: - # test to see if either the 1st column or the 2nd column (account for showindex) has - # the SEPARATING_LINE flag - if _is_separating_line(row): - _append_line(lines, padded_widths, colaligns, separating_line) - else: - append_row(lines, row, padded_widths, colaligns, fmt.datarow) + if fmt.linebelow and "linebelow" not in hidden: _append_line(lines, padded_widths, colaligns, fmt.linebelow) From d19c2fbe1468bc5c4634e90e2823cfa970874930 Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:55:55 -0300 Subject: [PATCH 2/5] WIP: Try to fix tabulate._CustomTextWrap._handle_long_word breaking up ANSI escape codes. --- tabulate/__init__.py | 14 +++++++++++--- test/test_textwrapper.py | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/tabulate/__init__.py b/tabulate/__init__.py index 11bb8655..8dca8d6e 100644 --- a/tabulate/__init__.py +++ b/tabulate/__init__.py @@ -2540,10 +2540,18 @@ def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): # take each charcter's width into account chunk = reversed_chunks[-1] i = 1 - while self._len(chunk[:i]) <= space_left: + while len(_strip_ansi(chunk)[:i]) <= space_left: i = i + 1 - cur_line.append(chunk[: i - 1]) - reversed_chunks[-1] = chunk[i - 1 :] + # Consider escape codes when breaking words up + total_escape_len = 0 + if _ansi_codes.search(chunk) is not None: + for group, _, _, _ in _ansi_codes.findall(chunk): + escape_len = len(group) + # FIXME: Needs to keep track of found groups and search from there + if group in chunk[: i + total_escape_len + escape_len - 1]: + total_escape_len += escape_len + cur_line.append(chunk[: i + total_escape_len - 1]) + reversed_chunks[-1] = chunk[i + total_escape_len - 1 :] # Otherwise, we have to preserve the long word intact. Only add # it to the current line if there's nothing already there -- diff --git a/test/test_textwrapper.py b/test/test_textwrapper.py index f3070b18..b02f5d15 100644 --- a/test/test_textwrapper.py +++ b/test/test_textwrapper.py @@ -3,7 +3,7 @@ import datetime -from tabulate import _CustomTextWrap as CTW, tabulate +from tabulate import _CustomTextWrap as CTW, tabulate, _strip_ansi from textwrap import TextWrapper as OTW from common import skip, assert_equal @@ -158,6 +158,42 @@ def test_wrap_color_line_splillover(): assert_equal(expected, result) +def test_wrap_color_line_longword(): + """TextWrapper: Wrap a line - preserve internal color tags and wrap them to + other lines when required, requires adding the colors tags to other lines as appropriate + and avoiding splitting escape codes.""" + data = "This_is_a_\033[31mtest_string_for_testing_TextWrap\033[0m_with_colors" + + expected = [ + "This_is_a_\033[31mte\033[0m", + "\033[31mst_string_fo\033[0m", + "\033[31mr_testing_Te\033[0m", + "\033[31mxtWrap\033[0m_with_", + "colors", + ] + wrapper = CTW(width=12) + result = wrapper.wrap(data) + assert_equal(expected, result) + + +def test_wrap_color_line_multiple_escapes(): + data = '012345(\x1b[32ma\x1b[0mbc\x1b[32mdefghij\x1b[0m)' + expected = [ + "012345(\x1b[32ma\x1b[0mbc\x1b[32mdefg\x1b[0m", + "\x1b[32mhij\x1b[0m)", + ] + wrapper = CTW(width=10) + result = wrapper.wrap(data) + assert_equal(expected, result) + clean_data = _strip_ansi(data) + for width in range(2, len(clean_data)): + # Currently fails with 14, 15 and 16, because a escape code gets split at the end + wrapper = CTW(width=width) + result = wrapper.wrap(data) + # print(width, result) + # Comparing after stripping ANSI should be enough to catch broken escape codes + assert_equal(clean_data, _strip_ansi("".join(result))) + def test_wrap_datetime(): """TextWrapper: Show that datetimes can be wrapped without crashing""" data = [ From d27d3668f5c06c7bfd85e0c51f88a65d63fbed34 Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Sun, 14 Jan 2024 14:58:05 -0300 Subject: [PATCH 3/5] Fix breaking up of long words so it doesn't mess up ANSI escape codes. --- tabulate/__init__.py | 7 +++++-- test/test_textwrapper.py | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tabulate/__init__.py b/tabulate/__init__.py index 8dca8d6e..f1561779 100644 --- a/tabulate/__init__.py +++ b/tabulate/__init__.py @@ -2540,16 +2540,19 @@ def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): # take each charcter's width into account chunk = reversed_chunks[-1] i = 1 + # Only count printable characters, so strip_ansi first, index later. while len(_strip_ansi(chunk)[:i]) <= space_left: i = i + 1 # Consider escape codes when breaking words up total_escape_len = 0 + last_group = 0 if _ansi_codes.search(chunk) is not None: for group, _, _, _ in _ansi_codes.findall(chunk): escape_len = len(group) - # FIXME: Needs to keep track of found groups and search from there - if group in chunk[: i + total_escape_len + escape_len - 1]: + if group in chunk[last_group: i + total_escape_len + escape_len - 1]: total_escape_len += escape_len + found = _ansi_codes.search(chunk[last_group:]) + last_group += found.end() cur_line.append(chunk[: i + total_escape_len - 1]) reversed_chunks[-1] = chunk[i + total_escape_len - 1 :] diff --git a/test/test_textwrapper.py b/test/test_textwrapper.py index b02f5d15..bdae8c8b 100644 --- a/test/test_textwrapper.py +++ b/test/test_textwrapper.py @@ -177,23 +177,23 @@ def test_wrap_color_line_longword(): def test_wrap_color_line_multiple_escapes(): - data = '012345(\x1b[32ma\x1b[0mbc\x1b[32mdefghij\x1b[0m)' + data = "012345(\x1b[32ma\x1b[0mbc\x1b[32mdefghij\x1b[0m)" expected = [ - "012345(\x1b[32ma\x1b[0mbc\x1b[32mdefg\x1b[0m", - "\x1b[32mhij\x1b[0m)", + "012345(\x1b[32ma\x1b[0mbc\x1b[32m\x1b[0m", + "\x1b[32mdefghij\x1b[0m)", ] wrapper = CTW(width=10) result = wrapper.wrap(data) assert_equal(expected, result) + clean_data = _strip_ansi(data) for width in range(2, len(clean_data)): - # Currently fails with 14, 15 and 16, because a escape code gets split at the end wrapper = CTW(width=width) result = wrapper.wrap(data) - # print(width, result) # Comparing after stripping ANSI should be enough to catch broken escape codes assert_equal(clean_data, _strip_ansi("".join(result))) + def test_wrap_datetime(): """TextWrapper: Show that datetimes can be wrapped without crashing""" data = [ From e1c59c325081e10f4ef9db86a357eb9cb5be448e Mon Sep 17 00:00:00 2001 From: devdanzin <74280297+devdanzin@users.noreply.github.com> Date: Mon, 15 Jan 2024 08:08:05 -0300 Subject: [PATCH 4/5] Make assert_equal() print repr() instead of str(). --- test/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/common.py b/test/common.py index 4cd37099..75524ecf 100644 --- a/test/common.py +++ b/test/common.py @@ -3,8 +3,8 @@ import warnings def assert_equal(expected, result): - print("Expected:\n%s\n" % expected) - print("Got:\n%s\n" % result) + print("Expected:\n%r\n" % expected) + print("Got:\n%r\n" % result) assert expected == result From 0ff2dff74aea7f5720caba0fc819a12cb4db61d3 Mon Sep 17 00:00:00 2001 From: "Helvio Junior (M4v3r1cK)" <34519097+helviojunior@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:29:47 -0300 Subject: [PATCH 5/5] Create version.py --- tabulate/version.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 tabulate/version.py diff --git a/tabulate/version.py b/tabulate/version.py new file mode 100644 index 00000000..68b492f2 --- /dev/null +++ b/tabulate/version.py @@ -0,0 +1 @@ +version=0.9.0