Skip to content

Commit 3b66d2d

Browse files
authored
Merge pull request #341 from dn813/master
Add table format "colon_grid"
2 parents 23e4fcd + 2855363 commit 3b66d2d

File tree

2 files changed

+165
-3
lines changed

2 files changed

+165
-3
lines changed

tabulate/__init__.py

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,30 @@ def _pipe_line_with_colons(colwidths, colaligns):
140140
return "|" + "|".join(segments) + "|"
141141

142142

143+
def _grid_segment_with_colons(colwidth, align):
144+
"""Return a segment of a horizontal line with optional colons which indicate
145+
column's alignment in a grid table."""
146+
width = colwidth
147+
if align == "right":
148+
return ("=" * (width - 1)) + ":"
149+
elif align == "center":
150+
return ":" + ("=" * (width - 2)) + ":"
151+
elif align == "left":
152+
return ":" + ("=" * (width - 1))
153+
else:
154+
return "=" * width
155+
156+
157+
def _grid_line_with_colons(colwidths, colaligns):
158+
"""Return a horizontal line with optional colons to indicate column's alignment
159+
in a grid table."""
160+
if not colaligns:
161+
colaligns = [""] * len(colwidths)
162+
segments = [_grid_segment_with_colons(w, a) for a, w in zip(colaligns, colwidths)]
163+
return "+" + "+".join(segments) + "+"
164+
165+
166+
143167
def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
144168
alignment = {
145169
"left": "",
@@ -406,6 +430,16 @@ def escape_empty(val):
406430
padding=1,
407431
with_header_hide=None,
408432
),
433+
"colon_grid": TableFormat(
434+
lineabove=Line("+", "-", "+", "+"),
435+
linebelowheader=_grid_line_with_colons,
436+
linebetweenrows=Line("+", "-", "+", "+"),
437+
linebelow=Line("+", "-", "+", "+"),
438+
headerrow=DataRow("|", "|", "|"),
439+
datarow=DataRow("|", "|", "|"),
440+
padding=1,
441+
with_header_hide=None,
442+
),
409443
"outline": TableFormat(
410444
lineabove=Line("+", "-", "+", "+"),
411445
linebelowheader=Line("+", "=", "+", "+"),
@@ -699,6 +733,7 @@ def escape_empty(val):
699733
"mixed_grid": "mixed_grid",
700734
"double_grid": "double_grid",
701735
"fancy_grid": "fancy_grid",
736+
"colon_grid": "colon_grid",
702737
"pipe": "pipe",
703738
"orgtbl": "orgtbl",
704739
"jira": "jira",
@@ -1838,6 +1873,30 @@ def tabulate(
18381873
│ eggs │ 451 │
18391874
╘═══════════╧═══════════╛
18401875
1876+
"colon_grid" is similar to "grid" but uses colons only to define
1877+
columnwise content alignment, with no whitespace padding:
1878+
1879+
>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1880+
... ["strings", "numbers"], "colon_grid"))
1881+
+-----------+-----------+
1882+
| strings | numbers |
1883+
+:==========+:==========+
1884+
| spam | 41.9999 |
1885+
+-----------+-----------+
1886+
| eggs | 451 |
1887+
+-----------+-----------+
1888+
1889+
>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
1890+
... ["strings", "numbers"], "colon_grid",
1891+
... colalign=["right", "left"]))
1892+
+-----------+-----------+
1893+
| strings | numbers |
1894+
+==========:+:==========+
1895+
| spam | 41.9999 |
1896+
+-----------+-----------+
1897+
| eggs | 451 |
1898+
+-----------+-----------+
1899+
18411900
"outline" is the same as the "grid" format but doesn't draw lines between rows:
18421901
18431902
>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
@@ -2154,6 +2213,13 @@ def tabulate(
21542213
numalign = "decimal" if numalign == _DEFAULT_ALIGN else numalign
21552214
stralign = "left" if stralign == _DEFAULT_ALIGN else stralign
21562215

2216+
# 'colon_grid' uses colons in the line beneath the header to represent a column's
2217+
# alignment instead of literally aligning the text differently. Hence,
2218+
# left alignment of the data in the text output is enforced.
2219+
if tablefmt == "colon_grid":
2220+
colglobalalign = "left"
2221+
headersglobalalign = "left"
2222+
21572223
# optimization: look for ANSI control codes once,
21582224
# enable smart width functions only if a control code is found
21592225
#
@@ -2222,7 +2288,7 @@ def tabulate(
22222288
aligns = [colglobalalign] * len(cols)
22232289
else: # default
22242290
aligns = [numalign if ct in [int, float] else stralign for ct in coltypes]
2225-
# then specific alignements
2291+
# then specific alignments
22262292
if colalign is not None:
22272293
assert isinstance(colalign, Iterable)
22282294
if isinstance(colalign, str):
@@ -2239,9 +2305,14 @@ def tabulate(
22392305
minwidths = (
22402306
[width_fn(h) + min_padding for h in headers] if headers else [0] * len(cols)
22412307
)
2308+
aligns_copy = aligns.copy()
2309+
# Reset alignments in copy of alignments list to "left" for 'colon_grid' format,
2310+
# which enforces left alignment in the text output of the data.
2311+
if tablefmt == "colon_grid":
2312+
aligns_copy = ["left"] * len(cols)
22422313
cols = [
22432314
_align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
2244-
for c, a, minw in zip(cols, aligns, minwidths)
2315+
for c, a, minw in zip(cols, aligns_copy, minwidths)
22452316
]
22462317

22472318
aligns_headers = None
@@ -2253,7 +2324,7 @@ def tabulate(
22532324
aligns_headers = [headersglobalalign] * len(t_cols)
22542325
else: # default
22552326
aligns_headers = aligns or [stralign] * len(headers)
2256-
# then specific header alignements
2327+
# then specific header alignments
22572328
if headersalign is not None:
22582329
assert isinstance(headersalign, Iterable)
22592330
if isinstance(headersalign, str):

test/test_output.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,97 @@ def test_fancy_grid_multiline_row_align():
14181418
assert_equal(expected, result)
14191419

14201420

1421+
def test_colon_grid():
1422+
"Output: colon_grid with two columns aligned left and center"
1423+
expected = "\n".join(
1424+
[
1425+
"+------+------+",
1426+
"| H1 | H2 |",
1427+
"+=====:+:====:+",
1428+
"| 3 | 4 |",
1429+
"+------+------+",
1430+
]
1431+
)
1432+
result = tabulate([[3, 4]], headers=("H1", "H2"), tablefmt="colon_grid", colalign=["right", "center"])
1433+
assert_equal(expected, result)
1434+
1435+
1436+
def test_colon_grid_wide_characters():
1437+
"Output: colon_grid with wide chars in header"
1438+
try:
1439+
import wcwidth # noqa
1440+
except ImportError:
1441+
skip("test_colon_grid_wide_characters is skipped")
1442+
headers = list(_test_table_headers)
1443+
headers[1] = "配列"
1444+
expected = "\n".join(
1445+
[
1446+
"+-----------+---------+",
1447+
"| strings | 配列 |",
1448+
"+:==========+========:+",
1449+
"| spam | 41.9999 |",
1450+
"+-----------+---------+",
1451+
"| eggs | 451 |",
1452+
"+-----------+---------+",
1453+
]
1454+
)
1455+
result = tabulate(_test_table, headers, tablefmt="colon_grid", colalign=["left", "right"])
1456+
assert_equal(expected, result)
1457+
1458+
1459+
def test_colon_grid_headerless():
1460+
"Output: colon_grid without headers"
1461+
expected = "\n".join(
1462+
[
1463+
"+------+---------+",
1464+
"| spam | 41.9999 |",
1465+
"+------+---------+",
1466+
"| eggs | 451 |",
1467+
"+------+---------+",
1468+
]
1469+
)
1470+
result = tabulate(_test_table, tablefmt="colon_grid")
1471+
assert_equal(expected, result)
1472+
1473+
1474+
def test_colon_grid_multiline():
1475+
"Output: colon_grid with multiline cells"
1476+
table = [["Data\n5", "33\n3"]]
1477+
headers = ["H1\n1", "H2\n2"]
1478+
expected = "\n".join(
1479+
[
1480+
"+------+------+",
1481+
"| H1 | H2 |",
1482+
"| 1 | 2 |",
1483+
"+:=====+:=====+",
1484+
"| Data | 33 |",
1485+
"| 5 | 3 |",
1486+
"+------+------+",
1487+
]
1488+
)
1489+
result = tabulate(table, headers, tablefmt="colon_grid")
1490+
assert_equal(expected, result)
1491+
1492+
1493+
def test_colon_grid_with_empty_cells():
1494+
table = [["A", ""], ["", "B"]]
1495+
headers = ["H1", "H2"]
1496+
alignments = ["center", "right"]
1497+
expected = "\n".join(
1498+
[
1499+
"+------+------+",
1500+
"| H1 | H2 |",
1501+
"+:====:+=====:+",
1502+
"| A | |",
1503+
"+------+------+",
1504+
"| | B |",
1505+
"+------+------+",
1506+
]
1507+
)
1508+
result = tabulate(table, headers, tablefmt="colon_grid", colalign=alignments)
1509+
assert_equal(expected, result)
1510+
1511+
14211512
def test_outline():
14221513
"Output: outline with headers"
14231514
expected = "\n".join(

0 commit comments

Comments
 (0)