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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,8 @@ linebetweenrows, linebelowheader, linebelow, lineabove or just a simple empty li
Moon 1737
----- ----

When using a custom `showindex`, supply one index per data row. Separating lines do not consume index values.

### ANSI support
ANSI escape codes are non-printable byte sequences usually used for terminal operations like setting
color output or modifying cursor positions. Because multi-byte ANSI sequences are inherently non-printable,
Expand Down
12 changes: 8 additions & 4 deletions tabulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1401,12 +1401,12 @@ def _prepend_row_index(rows, index):
"""Add a left-most index column."""
if index is None or index is False:
return rows
if isinstance(index, Sized) and len(index) != len(rows):
sans_rows, separating_lines = _remove_separating_lines(rows)
if isinstance(index, Sized) and len(index) != len(sans_rows):
raise ValueError(
"index must be as long as the number of data rows: "
f"len(index)={len(index)} len(rows)={len(rows)}"
f"len(index)={len(index)} len(rows)={len(sans_rows)}"
)
sans_rows, separating_lines = _remove_separating_lines(rows)
new_rows = []
index_iter = iter(index)
for row in sans_rows:
Expand Down Expand Up @@ -1589,6 +1589,10 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
# rows = list(map(list, rows))
rows = [r if _is_separating_line(r) else list(r) for r in rows]

# Keep DataFrame index labels aligned with data rows after removing separators.
if index is not None:
index = [value for row, value in zip(rows, index) if not _is_separating_line(row)]

# add or remove an index column
showindex_is_a_str = type(showindex) in [str, bytes]
if showindex_is_a_str and showindex == "default" and index is not None:
Expand All @@ -1599,7 +1603,7 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
rows = _prepend_row_index(rows, showindex)
elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str):
if index is None:
index = list(range(len(rows)))
index = list(range(len(_remove_separating_lines(rows)[0])))
rows = _prepend_row_index(rows, index)
elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str):
pass
Expand Down
58 changes: 57 additions & 1 deletion test/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from decimal import Decimal

from pytest import mark
from pytest import importorskip, mark

from tabulate import SEPARATING_LINE, simple_separated_format, tabulate

Expand Down Expand Up @@ -3248,6 +3248,62 @@ def test_list_of_lists_with_supplied_index():
tabulate(dd, headers=["a", "b"], showindex=[1, 2])


@mark.parametrize("index_factory", [list, tuple, iter])
@mark.parametrize(
("rows", "expected_lines"),
[
(
[["a"], SEPARATING_LINE, ["b"]],
["+----+---+", "| 10 | a |", "|----+---|", "| 20 | b |", "+----+---+"],
),
(
[["a"], [SEPARATING_LINE], SEPARATING_LINE, ["b"]],
[
"+----+---+",
"| 10 | a |",
"|----+---|",
"|----+---|",
"| 20 | b |",
"+----+---+",
],
),
],
)
def test_supplied_index_with_separating_lines(index_factory, rows, expected_lines):
"A separator does not consume a custom row index."
result = tabulate(rows, showindex=index_factory([10, 20]), tablefmt="psql")
assert_equal("\n".join(expected_lines), result)


@mark.parametrize("index", [[10], [10, 20, 30]])
def test_supplied_index_length_with_separating_lines(index):
"Index length validation counts data rows rather than separators."
rows = [["a"], SEPARATING_LINE, ["b"]]
with raises(ValueError):
tabulate(rows, showindex=index)


@mark.parametrize("showindex", ["default", "always", True])
def test_dataframe_index_with_separating_line(showindex):
"A separator keeps the original labels of the remaining DataFrame rows."
pd = importorskip("pandas")
frame = pd.DataFrame(
[["a", 1], [SEPARATING_LINE, None], ["b", 2]],
index=["row-a", "separator", "row-b"],
)
expected = "\n".join(
[
"+-------+---+---+",
"| row-a | a | 1 |",
"+-------+---+---+",
"+-------+---+---+",
"| row-b | b | 2 |",
"+-------+---+---+",
]
)
assert_equal(expected, tabulate(frame, showindex=showindex, tablefmt="grid"))


def test_list_of_lists_with_index_firstrow():
"Output: a table with a running index and header='firstrow'"
dd = zip(*[["a"] + list(range(3)), ["b"] + list(range(101, 104))])
Expand Down