Skip to content

Commit a7673f4

Browse files
committed
fix gregbanks#5: proper width of the colored values in columns
1 parent 0beecbb commit a7673f4

File tree

3 files changed

+51
-9
lines changed

3 files changed

+51
-9
lines changed

tabulate.py

+32-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import unicode_literals
77
from collections import namedtuple
88
from platform import python_version_tuple
9+
import re
910

1011

1112
if python_version_tuple()[0] < "3":
@@ -100,6 +101,10 @@
100101
with_header_hide=[],
101102
without_header_hide=["linebelowheader"])}
102103

104+
105+
_invisible_codes = re.compile("\x1b\[\d*m") # ANSI color codes
106+
107+
103108
def simple_separated_format(separator):
104109
"""Construct a simple TableFormat with columns separated by a separator.
105110
@@ -201,7 +206,8 @@ def _padleft(width, s):
201206
True
202207
203208
"""
204-
fmt = u"{0:>%ds}" % width
209+
iwidth = width + len(s) - len(_strip_invisible(s))
210+
fmt = u"{0:>%ds}" % iwidth
205211
return fmt.format(s)
206212

207213

@@ -212,7 +218,8 @@ def _padright(width, s):
212218
True
213219
214220
"""
215-
fmt = u"{0:<%ds}" % width
221+
iwidth = width + len(s) - len(_strip_invisible(s))
222+
fmt = u"{0:<%ds}" % iwidth
216223
return fmt.format(s)
217224

218225

@@ -223,10 +230,26 @@ def _padboth(width, s):
223230
True
224231
225232
"""
226-
fmt = u"{0:^%ds}" % width
233+
iwidth = width + len(s) - len(_strip_invisible(s))
234+
fmt = u"{0:^%ds}" % iwidth
227235
return fmt.format(s)
228236

229237

238+
def _strip_invisible(s):
239+
"Remove invisible ANSI color codes."
240+
return re.sub(_invisible_codes, "", s)
241+
242+
243+
def _visible_width(s):
244+
"""Visible width of a printed string. ANSI color codes are removed.
245+
246+
>>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world")
247+
(5, 5)
248+
249+
"""
250+
return len(_strip_invisible(s))
251+
252+
230253
def _align_column(strings, alignment, minwidth=0):
231254
"""[string] -> [padded_string]
232255
@@ -249,8 +272,9 @@ def _align_column(strings, alignment, minwidth=0):
249272
else:
250273
strings = [s.strip() for s in strings]
251274
padfn = _padright
252-
maxwidth = max(max(map(len, strings)), minwidth)
253-
return [padfn(maxwidth, s) for s in strings]
275+
maxwidth = max(max(map(_visible_width, strings)), minwidth)
276+
padded_strings = [padfn(maxwidth, s) for s in strings]
277+
return padded_strings
254278

255279

256280
def _more_generic(type1, type2):
@@ -459,18 +483,18 @@ def tabulate(list_of_lists, headers=[], tablefmt="simple",
459483

460484
# align columns
461485
aligns = [numalign if ct in [int,float] else stralign for ct in coltypes]
462-
minwidths = [len(h) + 2 for h in headers] if headers else [0]*len(cols)
486+
minwidths = [_visible_width(h)+2 for h in headers] if headers else [0]*len(cols)
463487
cols = [_align_column(c, a, minw)
464488
for c, a, minw in zip(cols, aligns, minwidths)]
465489

466490
if headers:
467491
# align headers and add headers
468-
minwidths = [max(minw, len(c[0])) for minw, c in zip(minwidths, cols)]
492+
minwidths = [max(minw, _visible_width(c[0])) for minw, c in zip(minwidths, cols)]
469493
headers = [_align_header(h, a, minw)
470494
for h, a, minw in zip(headers, aligns, minwidths)]
471495
rows = list(zip(*cols))
472496
else:
473-
minwidths = [len(c[0]) for c in cols]
497+
minwidths = [_visible_width(c[0]) for c in cols]
474498
rows = list(zip(*cols))
475499

476500
if not isinstance(tablefmt, TableFormat):

test_regression.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Regression tests."""
4+
5+
from __future__ import print_function
6+
from __future__ import unicode_literals
7+
from tabulate import tabulate
8+
9+
10+
11+
def test_ansi_color_in_table_cells():
12+
"ANSI color in table cells (issue #5)."
13+
colortable = [('test', '\x1b[31mtest\x1b[0m', '\x1b[32mtest\x1b[0m')]
14+
colorlessheaders = ('test', 'test', 'test')
15+
formattedtable = tabulate(colortable, colorlessheaders, 'pipe')
16+
correcttable = u'| test | test | test |\n|:-------|:-------|:-------|\n| test | \x1b[31mtest\x1b[0m | \x1b[32mtest\x1b[0m |'
17+
print("expected: %r\n\ngot: %r\n" % (correcttable, formattedtable))
18+
assert correcttable == formattedtable

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
envlist = py26, py27, py33
88

99
[testenv]
10-
commands = nosetests --with-doctest tabulate.py
10+
commands = nosetests --with-doctest tabulate.py test_regression.py
1111
deps =
1212
nose

0 commit comments

Comments
 (0)