Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add table format "colon_grid" #341

Merged
merged 5 commits into from
Sep 26, 2024
Merged
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
77 changes: 74 additions & 3 deletions tabulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,30 @@ def _pipe_line_with_colons(colwidths, colaligns):
return "|" + "|".join(segments) + "|"


def _grid_segment_with_colons(colwidth, align):
"""Return a segment of a horizontal line with optional colons which indicate
column's alignment in a grid table."""
width = colwidth
if align == "right":
return ("=" * (width - 1)) + ":"
elif align == "center":
return ":" + ("=" * (width - 2)) + ":"
elif align == "left":
return ":" + ("=" * (width - 1))
else:
return "=" * width


def _grid_line_with_colons(colwidths, colaligns):
"""Return a horizontal line with optional colons to indicate column's alignment
in a grid table."""
if not colaligns:
colaligns = [""] * len(colwidths)
segments = [_grid_segment_with_colons(w, a) for a, w in zip(colaligns, colwidths)]
return "+" + "+".join(segments) + "+"



def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
alignment = {
"left": "",
Expand Down Expand Up @@ -400,6 +424,16 @@ def escape_empty(val):
padding=1,
with_header_hide=None,
),
"colon_grid": TableFormat(
lineabove=Line("+", "-", "+", "+"),
linebelowheader=_grid_line_with_colons,
linebetweenrows=Line("+", "-", "+", "+"),
linebelow=Line("+", "-", "+", "+"),
headerrow=DataRow("|", "|", "|"),
datarow=DataRow("|", "|", "|"),
padding=1,
with_header_hide=None,
),
"outline": TableFormat(
lineabove=Line("+", "-", "+", "+"),
linebelowheader=Line("+", "=", "+", "+"),
Expand Down Expand Up @@ -693,6 +727,7 @@ def escape_empty(val):
"mixed_grid": "mixed_grid",
"double_grid": "double_grid",
"fancy_grid": "fancy_grid",
"colon_grid": "colon_grid",
"pipe": "pipe",
"orgtbl": "orgtbl",
"jira": "jira",
Expand Down Expand Up @@ -1822,6 +1857,30 @@ def tabulate(
│ eggs │ 451 │
╘═══════════╧═══════════╛

"colon_grid" is similar to "grid" but uses colons only to define
columnwise content alignment, with no whitespace padding:

>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
... ["strings", "numbers"], "colon_grid"))
+-----------+-----------+
| strings | numbers |
+:==========+:==========+
| spam | 41.9999 |
+-----------+-----------+
| eggs | 451 |
+-----------+-----------+

>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
... ["strings", "numbers"], "colon_grid",
... colalign=["right", "left"]))
+-----------+-----------+
| strings | numbers |
+==========:+:==========+
| spam | 41.9999 |
+-----------+-----------+
| eggs | 451 |
+-----------+-----------+

"outline" is the same as the "grid" format but doesn't draw lines between rows:

>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
Expand Down Expand Up @@ -2138,6 +2197,13 @@ def tabulate(
numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign
stralign = "left" if stralign == _DEFAULT_ALIGN else stralign

# 'colon_grid' uses colons in the line beneath the header to represent a column's
# alignment instead of literally aligning the text differently. Hence,
# left alignment of the data in the text output is enforced.
if tablefmt == "colon_grid":
colglobalalign = "left"
headersglobalalign = "left"

# optimization: look for ANSI control codes once,
# enable smart width functions only if a control code is found
#
Expand Down Expand Up @@ -2206,7 +2272,7 @@ def tabulate(
aligns = [colglobalalign] * len(cols)
else: # default
aligns = [numalign if ct in [int, float] else stralign for ct in coltypes]
# then specific alignements
# then specific alignments
if colalign is not None:
assert isinstance(colalign, Iterable)
if isinstance(colalign, str):
Expand All @@ -2219,9 +2285,14 @@ def tabulate(
minwidths = (
[width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols)
)
aligns_copy = aligns.copy()
# Reset alignments in copy of alignments list to "left" for 'colon_grid' format,
# which enforces left alignment in the text output of the data.
if tablefmt == "colon_grid":
aligns_copy = ["left"] * len(cols)
cols = [
_align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
for c, a, minw in zip(cols, aligns, minwidths)
for c, a, minw in zip(cols, aligns_copy, minwidths)
]

aligns_headers = None
Expand All @@ -2233,7 +2304,7 @@ def tabulate(
aligns_headers = [headersglobalalign] * len(t_cols)
else: # default
aligns_headers = aligns or [stralign] * len(headers)
# then specific header alignements
# then specific header alignments
if headersalign is not None:
assert isinstance(headersalign, Iterable)
if isinstance(headersalign, str):
Expand Down
91 changes: 91 additions & 0 deletions test/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,97 @@ def test_fancy_grid_multiline_row_align():
assert_equal(expected, result)


def test_colon_grid():
"Output: colon_grid with two columns aligned left and center"
expected = "\n".join(
[
"+------+------+",
"| H1 | H2 |",
"+=====:+:====:+",
"| 3 | 4 |",
"+------+------+",
]
)
result = tabulate([[3, 4]], headers=("H1", "H2"), tablefmt="colon_grid", colalign=["right", "center"])
assert_equal(expected, result)


def test_colon_grid_wide_characters():
"Output: colon_grid with wide chars in header"
try:
import wcwidth # noqa
except ImportError:
skip("test_colon_grid_wide_characters is skipped")
headers = list(_test_table_headers)
headers[1] = "配列"
expected = "\n".join(
[
"+-----------+---------+",
"| strings | 配列 |",
"+:==========+========:+",
"| spam | 41.9999 |",
"+-----------+---------+",
"| eggs | 451 |",
"+-----------+---------+",
]
)
result = tabulate(_test_table, headers, tablefmt="colon_grid", colalign=["left", "right"])
assert_equal(expected, result)


def test_colon_grid_headerless():
"Output: colon_grid without headers"
expected = "\n".join(
[
"+------+---------+",
"| spam | 41.9999 |",
"+------+---------+",
"| eggs | 451 |",
"+------+---------+",
]
)
result = tabulate(_test_table, tablefmt="colon_grid")
assert_equal(expected, result)


def test_colon_grid_multiline():
"Output: colon_grid with multiline cells"
table = [["Data\n5", "33\n3"]]
headers = ["H1\n1", "H2\n2"]
expected = "\n".join(
[
"+------+------+",
"| H1 | H2 |",
"| 1 | 2 |",
"+:=====+:=====+",
"| Data | 33 |",
"| 5 | 3 |",
"+------+------+",
]
)
result = tabulate(table, headers, tablefmt="colon_grid")
assert_equal(expected, result)


def test_colon_grid_with_empty_cells():
table = [["A", ""], ["", "B"]]
headers = ["H1", "H2"]
alignments = ["center", "right"]
expected = "\n".join(
[
"+------+------+",
"| H1 | H2 |",
"+:====:+=====:+",
"| A | |",
"+------+------+",
"| | B |",
"+------+------+",
]
)
result = tabulate(table, headers, tablefmt="colon_grid", colalign=alignments)
assert_equal(expected, result)


def test_outline():
"Output: outline with headers"
expected = "\n".join(
Expand Down
Loading