Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions tabulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2504,7 +2504,7 @@ def _pad_row(cells, padding):
return cells


def _build_simple_row(padded_cells: list[list], rowfmt: DataRow) -> str:
def _build_simple_row(padded_cells: list[list], rowfmt: DataRow, preserve_trailing_empty: bool = False) -> str:
"Format row according to DataRow format without padding."
begin = rowfmt.begin
sep = rowfmt.sep
Expand All @@ -2520,27 +2520,36 @@ def escape_char(c):
else:
escaped_cells = padded_cells

return (begin + sep.join(escaped_cells) + end).rstrip()
row = begin + sep.join(escaped_cells) + end
# When there is no closing delimiter and the last cell is purely whitespace
# (i.e. a missing/None value formatted as ""), rstrip() silently drops the
# trailing column separator and makes that column invisible. Skip rstrip()
# only for primary data rows (preserve_trailing_empty=True) so that
# multiline continuation lines are still stripped as before.
if preserve_trailing_empty and not end and escaped_cells and not escaped_cells[-1].strip():
return row
return row.rstrip()


def _build_row(
padded_cells: list[list],
colwidths: list[int],
colaligns: list[str],
rowfmt: DataRow | Callable,
preserve_trailing_empty: bool = False,
) -> str:
"Return a string which represents a row of data cells."
if not rowfmt:
return None
if callable(rowfmt):
return rowfmt(padded_cells, colwidths, colaligns)
else:
return _build_simple_row(padded_cells, rowfmt)
return _build_simple_row(padded_cells, rowfmt, preserve_trailing_empty)


def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt, rowalign=None):
def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt, rowalign=None, preserve_trailing_empty=False):
# NOTE: rowalign is ignored and exists for api compatibility with _append_multiline_row
lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt))
lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt, preserve_trailing_empty))
return lines


Expand Down Expand Up @@ -2624,7 +2633,9 @@ def _format_table(
append_row = partial(_append_multiline_row, pad=pad)
else:
pad_row = _pad_row
append_row = _append_basic_row
# preserve_trailing_empty=True so that a trailing None/empty cell (which
# formats to "") is not silently rstripped away and the column disappears.
append_row = partial(_append_basic_row, preserve_trailing_empty=True)

padded_headers = pad_row(headers, pad)

Expand Down
10 changes: 5 additions & 5 deletions test/test_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_list_of_lists():
" string number",
"-- -------- --------",
"a one 1",
"b two",
"b two ",
]
)
result = tabulate(ll, headers=["string", "number"])
Expand All @@ -72,7 +72,7 @@ def test_list_of_lists_firstrow():
" string number",
"-- -------- --------",
"a one 1",
"b two",
"b two ",
]
)
result = tabulate(ll, headers="firstrow")
Expand All @@ -82,7 +82,7 @@ def test_list_of_lists_firstrow():
def test_list_of_lists_keys():
"Input: a list of lists with column indices as headers."
ll = [["a", "one", 1], ["b", "two", None]]
expected = "\n".join(["0 1 2", "--- --- ---", "a one 1", "b two"])
expected = "\n".join(["0 1 2", "--- --- ---", "a one 1", "b two "])
result = tabulate(ll, headers="keys")
assert_equal(expected, result)

Expand Down Expand Up @@ -405,8 +405,8 @@ def test_list_of_dicts_with_missing_keys():
[
" foo bar baz",
"----- ----- -----",
" 1",
" 2",
" 1 ",
" 2 ",
" 4 3",
]
)
Expand Down
4 changes: 2 additions & 2 deletions test/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -3028,7 +3028,7 @@ def test_missingval_multi():
missingval=("n/a", "?"),
tablefmt="plain",
)
expected = "Alice Bob Charlie\nn/a ?"
expected = "Alice Bob Charlie\nn/a ? "
assert_equal(expected, result)


Expand All @@ -3053,7 +3053,7 @@ def test_column_emptymissing_deduction():
? 1.2342 1/3
0.056789 12,345 abc
?
3,333.3 ?
3,333.3 ?
------------ ----------- ---"""
assert_equal(expected, result)

Expand Down
4 changes: 2 additions & 2 deletions test/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def test_88_256_ANSI_color_codes():

def test_column_with_mixed_value_types():
"Regression: mixed value types in the same column (issue #31)"
expected = "\n".join(["-----", "", "a", "я", "0", "False", "-----"])
expected = "\n".join(["-----", " ", "a", "я", "0", "False", "-----"])
data = [[None], ["a"], ["\u044f"], [0], [False]]
table = tabulate(data)
assert_equal(table, expected)
Expand Down Expand Up @@ -415,7 +415,7 @@ def test_escape_empty_cell_in_first_column_in_rst():
def test_ragged_rows():
"Regression: allow rows with different number of columns (issue #85)"
table = [[1, 2, 3], [1, 2], [1, 2, 3, 4]]
expected = "\n".join(["- - - -", "1 2 3", "1 2", "1 2 3 4", "- - - -"])
expected = "\n".join(["- - - -", "1 2 3 ", "1 2 ", "1 2 3 4", "- - - -"])
result = tabulate(table)
assert_equal(expected, result)

Expand Down