20"""module for creating simple ASCII tables
26 table.set_cols_align(["l", "r", "c"])
27 table.set_cols_valign(["t", "m", "b"])
28 table.add_rows([ ["Name", "Age", "Nickname"],
29 ["Mr\\nXavier\\nHuon", 32, "Xav'"],
30 ["Mr\\nBaptiste\\nClement", 1, "Baby"] ])
31 print table.draw() + "\\n"
34 table.set_deco(Texttable.HEADER)
35 table.set_cols_dtype(['t', # text
36 'f', # float (decimal)
37 'e', # float (exponent)
40 table.set_cols_align(["l", "r", "r", "r", "l"])
41 table.add_rows([["text", "float", "exp", "int", "auto"],
42 ["abcd", "67", 654, 89, 128.001],
43 ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023],
44 ["lmn", 5e-78, 5e-78, 89.4, .000000000000128],
45 ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]])
50 +----------+-----+----------+
51 | Name | Age | Nickname |
52 +==========+=====+==========+
56 +----------+-----+----------+
60 +----------+-----+----------+
62 text float exp int auto
63 ===========================================
64 abcd 67.000 6.540e+02 89 128.001
65 efgh 67.543 6.540e-01 90 1.280e+22
66 ijkl 0.000 5.000e-78 89 0.000
67 mnop 0.023 5.000e+78 92 1.280e+22
70__all__ = [
"Texttable",
"ArraySizeError"]
72__author__ =
'Gerome Fournier <jef(at)foutaise.org>'
77 - textwrap improved import
78 - comment concerning header output
81 - add_rows method, for adding rows in one go
84 - redefined len() function to deal with non-ASCII characters
87 - columns datatype specifications
90 - better handling of unicode errors
97from functools
import reduce
100 if sys.version >=
'2.3':
102 elif sys.version >=
'2.2':
103 from optparse
import textwrap
105 from optik
import textwrap
107 sys.stderr.write(
"Can't import textwrap module!\n")
111 """Redefining len here so it will be able to work with non-ASCII characters
113 if not isinstance(iterable, str):
114 return iterable.__len__()
117 return len(str(iterable,
'utf'))
119 return iterable.__len__()
122 """Exception raised when specified rows don't fit the required size
127 Exception.__init__(self, msg,
'')
141 UNDERLINE =
'\x1b[4m'
145 if type == bcolors.WHITE:
147 return '%s%s%s' % (type, string, end)
159 - max_width is an integer, specifying the maximum width of the table
160 - if set to 0, size is unlimited, therefore cells won't be wrapped
168 self.
_deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \
174 """Reset the instance
176 - reset rows and header
185 """Set the characters used to draw lines between rows and columns
187 - the array should contain 4 fields:
189 [horizontal, vertical, corner, header]
198 array = [ x[:1]
for x
in [ str(s)
for s
in array ] ]
203 """Set the table decoration
205 - 'deco' can be a combinaison of:
207 Texttable.BORDER: Border around the table
208 Texttable.HEADER: Horizontal line below the header
209 Texttable.HLINES: Horizontal lines between rows
210 Texttable.VLINES: Vertical lines between columns
212 All of them are enabled by default
216 Texttable.BORDER | Texttable.HEADER
222 """Set the desired columns alignment
224 - the elements of the array should be either "l", "c" or "r":
226 * "l": column flushed left
227 * "c": column centered
228 * "r": column flushed right
235 """Set the desired columns vertical alignment
237 - the elements of the array should be either "t", "m" or "b":
239 * "t": column aligned on the top of the cell
240 * "m": column aligned on the middle of the cell
241 * "b": column aligned on the bottom of the cell
248 """Set the desired columns datatype for the cols.
250 - the elements of the array should be either "a", "t", "f", "e" or "i":
252 * "a": automatic (try to use the most appropriate datatype)
254 * "f": treat as float in decimal format
255 * "e": treat as float in exponential format
258 - by default, automatic datatyping is used for each column
265 """Set the desired columns width
267 - the elements of the array should be integers, specifying the
268 width of each column. For example:
275 array = list(map(int, array))
276 if reduce(min, array) <= 0:
279 sys.stderr.write(
"Wrong argument in column width specification\n")
284 """Set the desired precision for float/exponential formats
286 - width must be an integer >= 0
288 - default value is set to 3
291 if not type(width)
is int
or width < 0:
292 raise ValueError(
'width must be an integer greater then 0')
296 """Specify the header of the table
300 self.
_header = list(map(str, array))
303 """Add a row in the rows stack
305 - cells can contain newlines and tabs
310 if not hasattr(self,
"_dtype"):
314 for i,x
in enumerate(array):
315 cells.append(self.
_str(i,x))
319 """Add several rows in the rows stack
321 - The 'rows' argument can be either an iterator returning arrays,
322 or a by-dimensional array
323 - 'header' specifies if the first row should be used as the header
330 if hasattr(rows,
'__iter__')
and hasattr(rows,
'next'):
342 - the table is returned as a whole string
367 """Handles string formatting of cell data
369 i - index of the cell datatype in self._dtype
370 x - cell data to format
375 if n ==
"nan" or n==
"inf" or n==
"-inf" :
raise ValueError(
'Infinity or NaN considered as string')
383 return str(x.encode(
'utf-8'))
389 return str(int(round(f)))
391 return '%.*f' % (n, f)
393 return '%.*e' % (n, f)
401 return str(x.encode(
'utf-8'))
403 if f - round(f) == 0:
405 return '%.*e' % (n, f)
407 return str(int(round(f)))
410 return '%.*e' % (n, f)
412 return '%.*f' % (n, f)
415 """Check that the specified array fits the previous rows size
425 """Return a boolean, if vlines are required or not
428 return self.
_deco & Texttable.VLINES > 0
431 """Return a boolean, if hlines are required or not
434 return self.
_deco & Texttable.HLINES > 0
437 """Return a boolean, if border is required or not
440 return self.
_deco & Texttable.BORDER > 0
443 """Return a boolean, if header line is required or not
446 return self.
_deco & Texttable.HEADER > 0
449 """Print header's horizontal line
455 """Print an horizontal line
463 """Return a string used to separated rows or separate header from
473 l = s.join([horiz * n
for n
in self.
_width])
476 l =
"%s%s%s%s%s\n" % (self.
_char_corner, horiz, l, horiz,
483 """Return the width of the cell
485 Special characters are taken into account to return the width of the
486 cell, such like newlines and tabs
489 cell = re.compile(
r'\x1b[^m]*m').sub(
'', cell)
491 cell_lines = cell.split(
'\n')
493 for line
in cell_lines:
495 parts = line.split(
'\t')
496 for part, i
in zip(parts, list(range(1,
len(parts) + 1))):
497 length = length +
len(part)
499 length = (length//8 + 1) * 8
500 maxi = max(maxi, length)
504 """Return an array with the width of each column
506 If a specific width has been specified, exit. If the total of the
507 columns width exceed the table desired width, another width will be
508 computed to fit, and cells will be wrapped.
511 if hasattr(self,
"_width"):
517 for cell,i
in zip(row, list(range(
len(row)))):
519 maxi[i] = max(maxi[i], self.
_len_cell(cell))
520 except (TypeError, IndexError):
523 length = reduce(
lambda x,y: x+y, maxi)
526 maxi = [(self.
_max_width - items * 3 -1) // items \
527 for n
in range(items)]
537 for col, max_len
in enumerate(max_lengths):
538 current_length = maxi[col]
542 if current_length > max_len:
543 free += current_length - max_len
547 elif max_len > current_length:
553 free_part = int(math.ceil(float(free) / float(oversized)))
555 for col, max_len
in enumerate(max_lengths):
556 current_length = maxi[col]
559 if current_length < max_len:
562 needed = max_len - current_length
565 if needed <= free_part:
572 maxi[col] = maxi[col] + free_part
577 """Check if alignment has been specified, set default one if not
580 if not hasattr(self,
"_align"):
582 if not hasattr(self,
"_valign"):
588 Loop over a single cell length, over all the cells
591 line = self.
_splitit(line, isheader)
594 for i
in range(
len(line[0])):
598 for cell, width, align
in zip(line, self.
_width, self.
_align):
602 fill = width -
len(re.compile(
r'\x1b[^m]*m').sub(
'', cell_line))
606 out +=
"%s " % (fill * space + cell_line)
608 out +=
"%s " % (fill//2 * space + cell_line \
609 + (fill//2 + fill%2) * space)
611 out +=
"%s " % (cell_line + fill * space)
612 if length <
len(line):
618 """Split each element of line to fit the column width
620 Each element is turned into a list, result of the wrapping of the
621 string to the desired width
625 for cell, width
in zip(line, self.
_width):
629 for c
in cell.split(
'\n'):
630 c =
"".join(ansi_keep) + c
633 for a
in re.findall(
r'\x1b[^m]*m', c):
634 extra_width +=
len(a)
636 if len(ansi_keep) > 0:
640 c = c +
'\x1b[0m' *
len(ansi_keep)
641 extra_width +=
len(
'\x1b[0m' *
len(ansi_keep))
642 if type(c)
is not str:
645 except UnicodeDecodeError
as strerror:
646 sys.stderr.write(
"UnicodeDecodeError exception for string '%s': %s\n" % (c, strerror))
647 c = str(c,
'utf',
'replace')
648 array.extend(textwrap.wrap(c, width + extra_width))
649 line_wrapped.append(array)
650 max_cell_lines = reduce(max, list(map(len, line_wrapped)))
651 for cell, valign
in zip(line_wrapped, self.
_valign):
655 missing = max_cell_lines -
len(cell)
656 cell[:0] = [
""] * (missing // 2)
657 cell.extend([
""] * (missing // 2 + missing % 2))
659 cell[:0] = [
""] * (max_cell_lines -
len(cell))
661 cell.extend([
""] * (max_cell_lines -
len(cell)))
664if __name__ ==
'__main__':
666 table.set_cols_align([
"l",
"r",
"c"])
667 table.set_cols_valign([
"t",
"m",
"b"])
671 print(table.draw() +
"\n")
674 table.set_deco(Texttable.HEADER)
675 table.set_cols_dtype([
't',
680 table.set_cols_align([
"l",
"r",
"r",
"r",
"l"])
681 table.add_rows([[
'text',
"float",
"exp",
"int",
"auto"],
682 [
"abcd",
"67", 654, 89, 128.001],
683 [
"efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023],
684 [
"lmn", 5e-78, 5e-78, 89.4, .000000000000128],
685 [
"opqrstu", .023, 5e+78, 92., 12800000000000000000000]])
_draw_line(self, line, isheader=False)
_build_hline(self, is_header=False)
set_cols_dtype(self, array)
_check_row_size(self, array)
set_cols_valign(self, array)
set_precision(self, width)
_splitit(self, line, isheader)
set_cols_align(self, array)
set_cols_width(self, array)
_compute_cols_width(self)
__init__(self, max_width=80)
add_rows(self, rows, header=True)
get_color_string(type, string)