Skip to content
Draft
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
14 changes: 12 additions & 2 deletions tabulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1626,12 +1626,18 @@ def _wrap_text_to_colwidths(
else:
num_cols = 0
numparses = _expand_iterable(numparses, num_cols, True)
if isinstance(missingval, str):
missing_vals = [missingval] * num_cols
else:
missing_vals = list(missingval)
if len(missing_vals) < num_cols:
missing_vals.extend([_DEFAULT_MISSINGVAL] * (num_cols - len(missing_vals)))

result = []

for row in list_of_lists:
new_row = []
for cell, width, numparse in zip(row, colwidths, numparses):
for cell, width, numparse, miss_v in zip(row, colwidths, numparses, missing_vals):
if _isnumber(cell) and numparse:
new_row.append(cell)
continue
Expand All @@ -1647,7 +1653,7 @@ def _wrap_text_to_colwidths(
# explicit than just `str` of the object. Also doesn't work for
# custom floatfmt/intfmt, nor with any missing/blank cells.
casted_cell = (
missingval
miss_v
if cell is None
else (
str(cell)
Expand Down Expand Up @@ -2242,6 +2248,10 @@ def tabulate(
)
list_of_lists, separating_lines = _remove_separating_lines(list_of_lists)

# Reuse per-column markers across cell wrapping, header wrapping, and formatting.
if not isinstance(missingval, str):
missingval = list(missingval)

if maxcolwidths is not None:
if type(maxcolwidths) is tuple: # Check if tuple, convert to list if so
maxcolwidths = list(maxcolwidths)
Expand Down
66 changes: 66 additions & 0 deletions test/test_textwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import datetime
from textwrap import TextWrapper as OTW

import pytest

from tabulate import _CustomTextWrap as CTW, _strip_ansi, tabulate

from common import assert_equal, skip
Expand Down Expand Up @@ -302,6 +304,70 @@ def test_wrap_none_value_with_missingval():
assert_equal(expected, result)


@pytest.mark.parametrize("container", [list, tuple, iter])
@pytest.mark.parametrize(
"markers, widths, expected_data",
[
(["?", "N/A"], [10, 10], [["?", "N/A"], ["Alice", "N/A"]]),
(["?"], [10, 10], [["?", ""], ["Alice", ""]]),
([], [10, 10], [["", ""], ["Alice", ""]]),
(
["not available", "missing value"],
[7, 6],
[["not available", "missing value"], ["Alice", "missing value"]],
),
(
["not available", "missing value"],
[None, 6],
[["not available", "missing value"], ["Alice", "missing value"]],
),
],
)
def test_wrap_none_value_with_per_column_missingval(container, markers, widths, expected_data):
"""Wrapping uses each column's marker, padding short sequences with empty strings."""
result = tabulate(
[[None, None], ["Alice", None]],
tablefmt="plain",
maxcolwidths=widths,
missingval=container(markers),
)
expected = tabulate(expected_data, tablefmt="plain", maxcolwidths=widths)
assert_equal(expected, result)


@pytest.mark.parametrize(
"width_options",
[
{"maxcolwidths": [None, None]},
{"maxheadercolwidths": 5},
{"maxcolwidths": [5, 5], "maxheadercolwidths": 5},
{},
],
)
def test_wrap_none_value_with_missingval_iterator(width_options):
"""Column markers remain available after wrapping cells and/or headers."""
data = [[None, "present"]]
headers = ["First column", "Second column"]
result = tabulate(
data,
headers=headers,
tablefmt="plain",
missingval=iter(["?", "N/A"]),
**width_options,
)
expected = tabulate([["?", "present"]], headers=headers, tablefmt="plain", **width_options)
assert_equal(expected, result)


@pytest.mark.parametrize("missingval", ["?", ["?", "N/A"], ("?", "N/A")])
def test_wrap_per_column_missingval_without_missing_cells(missingval):
"""Missing-value markers do not affect non-missing cells."""
data = [["First Entry", "present"], ["Second Entry", "available"]]
result = tabulate(data, tablefmt="plain", maxcolwidths=[7, 5], missingval=missingval)
expected = tabulate(data, tablefmt="plain", maxcolwidths=[7, 5])
assert_equal(expected, result)


def test_wrap_optional_bool_strs():
"""TextWrapper: Show that str bools and None can be wrapped without crashing"""
data = [
Expand Down