6
6
from __future__ import unicode_literals
7
7
from collections import namedtuple
8
8
from platform import python_version_tuple
9
+ import re
9
10
10
11
11
12
if python_version_tuple ()[0 ] < "3" :
100
101
with_header_hide = [],
101
102
without_header_hide = ["linebelowheader" ])}
102
103
104
+
105
+ _invisible_codes = re .compile ("\x1b \[\d*m" ) # ANSI color codes
106
+
107
+
103
108
def simple_separated_format (separator ):
104
109
"""Construct a simple TableFormat with columns separated by a separator.
105
110
@@ -201,7 +206,8 @@ def _padleft(width, s):
201
206
True
202
207
203
208
"""
204
- fmt = u"{0:>%ds}" % width
209
+ iwidth = width + len (s ) - len (_strip_invisible (s ))
210
+ fmt = u"{0:>%ds}" % iwidth
205
211
return fmt .format (s )
206
212
207
213
@@ -212,7 +218,8 @@ def _padright(width, s):
212
218
True
213
219
214
220
"""
215
- fmt = u"{0:<%ds}" % width
221
+ iwidth = width + len (s ) - len (_strip_invisible (s ))
222
+ fmt = u"{0:<%ds}" % iwidth
216
223
return fmt .format (s )
217
224
218
225
@@ -223,10 +230,26 @@ def _padboth(width, s):
223
230
True
224
231
225
232
"""
226
- fmt = u"{0:^%ds}" % width
233
+ iwidth = width + len (s ) - len (_strip_invisible (s ))
234
+ fmt = u"{0:^%ds}" % iwidth
227
235
return fmt .format (s )
228
236
229
237
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
+
230
253
def _align_column (strings , alignment , minwidth = 0 ):
231
254
"""[string] -> [padded_string]
232
255
@@ -249,8 +272,9 @@ def _align_column(strings, alignment, minwidth=0):
249
272
else :
250
273
strings = [s .strip () for s in strings ]
251
274
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
254
278
255
279
256
280
def _more_generic (type1 , type2 ):
@@ -459,18 +483,18 @@ def tabulate(list_of_lists, headers=[], tablefmt="simple",
459
483
460
484
# align columns
461
485
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 )
463
487
cols = [_align_column (c , a , minw )
464
488
for c , a , minw in zip (cols , aligns , minwidths )]
465
489
466
490
if headers :
467
491
# 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 )]
469
493
headers = [_align_header (h , a , minw )
470
494
for h , a , minw in zip (headers , aligns , minwidths )]
471
495
rows = list (zip (* cols ))
472
496
else :
473
- minwidths = [len (c [0 ]) for c in cols ]
497
+ minwidths = [_visible_width (c [0 ]) for c in cols ]
474
498
rows = list (zip (* cols ))
475
499
476
500
if not isinstance (tablefmt , TableFormat ):
0 commit comments