DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
click_texttable.py
Go to the documentation of this file.
1#!/usr/bin/env python
2#
3# texttable - module for creating simple ASCII tables
4# Copyright (C) 2003-2011 Gerome Fournier <jef(at)foutaise.org>
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20"""module for creating simple ASCII tables
21
22
23Example:
24
25 table = Texttable()
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"
32
33 table = Texttable()
34 table.set_deco(Texttable.HEADER)
35 table.set_cols_dtype(['t', # text
36 'f', # float (decimal)
37 'e', # float (exponent)
38 'i', # integer
39 'a']) # automatic
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]])
46 print table.draw()
47
48Result:
49
50 +----------+-----+----------+
51 | Name | Age | Nickname |
52 +==========+=====+==========+
53 | Mr | | |
54 | Xavier | 32 | |
55 | Huon | | Xav' |
56 +----------+-----+----------+
57 | Mr | | |
58 | Baptiste | 1 | |
59 | Clement | | Baby |
60 +----------+-----+----------+
61
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
68"""
69
70__all__ = ["Texttable", "ArraySizeError"]
71
72__author__ = 'Gerome Fournier <jef(at)foutaise.org>'
73__license__ = 'GPL'
74__version__ = '0.8.1'
75__credits__ = """\
76Jeff Kowalczyk:
77 - textwrap improved import
78 - comment concerning header output
79
80Anonymous:
81 - add_rows method, for adding rows in one go
82
83Sergey Simonenko:
84 - redefined len() function to deal with non-ASCII characters
85
86Roger Lew:
87 - columns datatype specifications
88
89Brian Peterson:
90 - better handling of unicode errors
91"""
92
93import re
94import math
95import sys
96import string
97from functools import reduce
98
99try:
100 if sys.version >= '2.3':
101 import textwrap
102 elif sys.version >= '2.2':
103 from optparse import textwrap
104 else:
105 from optik import textwrap
106except ImportError:
107 sys.stderr.write("Can't import textwrap module!\n")
108 raise
109
110def len(iterable):
111 """Redefining len here so it will be able to work with non-ASCII characters
112 """
113 if not isinstance(iterable, str):
114 return iterable.__len__()
115
116 try:
117 return len(str(iterable, 'utf'))
118 except:
119 return iterable.__len__()
120
121class ArraySizeError(Exception):
122 """Exception raised when specified rows don't fit the required size
123 """
124
125 def __init__(self, msg):
126 self.msg = msg
127 Exception.__init__(self, msg, '')
128
129 def __str__(self):
130 return self.msg
131
133 PURPLE = '\x1b[95m'
134 BLUE = '\x1b[94m'
135 GREEN = '\x1b[92m'
136 YELLOW = '\x1b[93m'
137 RED = '\x1b[91m'
138 ENDC = '\x1b[0m'
139 WHITE = ''
140 BOLD = '\x1b[1m'
141 UNDERLINE = '\x1b[4m'
142
143def get_color_string(type, string):
144 end = bcolors.ENDC
145 if type == bcolors.WHITE:
146 end = ''
147 return '%s%s%s' % (type, string, end)
148
150
151 BORDER = 1
152 HEADER = 1 << 1
153 HLINES = 1 << 2
154 VLINES = 1 << 3
155
156 def __init__(self, max_width=80):
157 """Constructor
158
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
161 """
162
163 if max_width <= 0:
164 max_width = False
165 self._max_width = max_width
166 self._precision = 3
167
168 self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \
169 Texttable.HEADER
170 self.set_chars(['-', '|', '+', '='])
171 self.reset()
172
173 def reset(self):
174 """Reset the instance
175
176 - reset rows and header
177 """
178
179 self._hline_string = None
180 self._row_size = None
181 self._header = []
183
184 def set_chars(self, array):
185 """Set the characters used to draw lines between rows and columns
186
187 - the array should contain 4 fields:
188
189 [horizontal, vertical, corner, header]
190
191 - default is set to:
192
193 ['-', '|', '+', '=']
194 """
195
196 if len(array) != 4:
197 raise ArraySizeError("array should contain 4 characters")
198 array = [ x[:1] for x in [ str(s) for s in array ] ]
200 self._char_corner, self._char_header) = array
201
202 def set_deco(self, deco):
203 """Set the table decoration
204
205 - 'deco' can be a combinaison of:
206
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
211
212 All of them are enabled by default
213
214 - example:
215
216 Texttable.BORDER | Texttable.HEADER
217 """
218
219 self._deco = deco
220
221 def set_cols_align(self, array):
222 """Set the desired columns alignment
223
224 - the elements of the array should be either "l", "c" or "r":
225
226 * "l": column flushed left
227 * "c": column centered
228 * "r": column flushed right
229 """
230
231 self._check_row_size(array)
232 self._align = array
233
234 def set_cols_valign(self, array):
235 """Set the desired columns vertical alignment
236
237 - the elements of the array should be either "t", "m" or "b":
238
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
242 """
243
244 self._check_row_size(array)
245 self._valign = array
246
247 def set_cols_dtype(self, array):
248 """Set the desired columns datatype for the cols.
249
250 - the elements of the array should be either "a", "t", "f", "e" or "i":
251
252 * "a": automatic (try to use the most appropriate datatype)
253 * "t": treat as text
254 * "f": treat as float in decimal format
255 * "e": treat as float in exponential format
256 * "i": treat as int
257
258 - by default, automatic datatyping is used for each column
259 """
260
261 self._check_row_size(array)
262 self._dtype = array
263
264 def set_cols_width(self, array):
265 """Set the desired columns width
266
267 - the elements of the array should be integers, specifying the
268 width of each column. For example:
269
270 [10, 20, 5]
271 """
272
273 self._check_row_size(array)
274 try:
275 array = list(map(int, array))
276 if reduce(min, array) <= 0:
277 raise ValueError
278 except ValueError:
279 sys.stderr.write("Wrong argument in column width specification\n")
280 raise
281 self._width = array
282
283 def set_precision(self, width):
284 """Set the desired precision for float/exponential formats
285
286 - width must be an integer >= 0
287
288 - default value is set to 3
289 """
290
291 if not type(width) is int or width < 0:
292 raise ValueError('width must be an integer greater then 0')
293 self._precision = width
294
295 def header(self, array):
296 """Specify the header of the table
297 """
298
299 self._check_row_size(array)
300 self._header = list(map(str, array))
301
302 def add_row(self, array):
303 """Add a row in the rows stack
304
305 - cells can contain newlines and tabs
306 """
307
308 self._check_row_size(array)
309
310 if not hasattr(self, "_dtype"):
311 self._dtype = ["a"] * self._row_size
312
313 cells = []
314 for i,x in enumerate(array):
315 cells.append(self._str(i,x))
316 self._rows_rows_rows.append(cells)
317
318 def add_rows(self, rows, header=True):
319 """Add several rows in the rows stack
320
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
324 of the table
325 """
326
327 # nb: don't use 'iter' on by-dimensional arrays, to get a
328 # usable code for python 2.1
329 if header:
330 if hasattr(rows, '__iter__') and hasattr(rows, 'next'):
331 self.header(next(rows))
332 else:
333 self.header(rows[0])
334 rows = rows[1:]
335 for row in rows:
336 self.add_row(row)
337
338
339 def draw(self):
340 """Draw the table
341
342 - the table is returned as a whole string
343 """
344
345 if not self._header and not self._rows_rows_rows:
346 return
348 self._check_align()
349 out = ""
350 if self._has_border():
351 out += self._hline()
352 if self._header:
353 out += self._draw_line(self._header, isheader=True)
354 if self._has_header():
355 out += self._hline_header()
356 length = 0
357 for row in self._rows_rows_rows:
358 length += 1
359 out += self._draw_line(row)
360 if self._has_hlines() and length < len(self._rows_rows_rows):
361 out += self._hline()
362 if self._has_border():
363 out += self._hline()
364 return out[:-1]
365
366 def _str(self, i, x):
367 """Handles string formatting of cell data
368
369 i - index of the cell datatype in self._dtype
370 x - cell data to format
371 """
372 try:
373 f = float(x)
374 n = str(f)
375 if n == "nan" or n=="inf" or n=="-inf" : raise ValueError('Infinity or NaN considered as string')
376 except:
377 if type(x) is str:
378 return x
379 else:
380 if x is None:
381 return str(x)
382 else:
383 return str(x.encode('utf-8'))
384
385 n = self._precision
386 dtype = self._dtype[i]
387
388 if dtype == 'i':
389 return str(int(round(f)))
390 elif dtype == 'f':
391 return '%.*f' % (n, f)
392 elif dtype == 'e':
393 return '%.*e' % (n, f)
394 elif dtype == 't':
395 if type(x) is str:
396 return x
397 else:
398 if x is None:
399 return str(x)
400 else:
401 return str(x.encode('utf-8'))
402 else:
403 if f - round(f) == 0:
404 if abs(f) > 1e8:
405 return '%.*e' % (n, f)
406 else:
407 return str(int(round(f)))
408 else:
409 if abs(f) > 1e8:
410 return '%.*e' % (n, f)
411 else:
412 return '%.*f' % (n, f)
413
414 def _check_row_size(self, array):
415 """Check that the specified array fits the previous rows size
416 """
417
418 if not self._row_size:
419 self._row_size = len(array)
420 elif self._row_size != len(array):
421 raise ArraySizeError("array should contain %d elements" \
422 % self._row_size)
423
424 def _has_vlines(self):
425 """Return a boolean, if vlines are required or not
426 """
427
428 return self._deco & Texttable.VLINES > 0
429
430 def _has_hlines(self):
431 """Return a boolean, if hlines are required or not
432 """
433
434 return self._deco & Texttable.HLINES > 0
435
436 def _has_border(self):
437 """Return a boolean, if border is required or not
438 """
439
440 return self._deco & Texttable.BORDER > 0
441
442 def _has_header(self):
443 """Return a boolean, if header line is required or not
444 """
445
446 return self._deco & Texttable.HEADER > 0
447
448 def _hline_header(self):
449 """Print header's horizontal line
450 """
451
452 return self._build_hline(True)
453
454 def _hline(self):
455 """Print an horizontal line
456 """
457
458 if not self._hline_string:
459 self._hline_string = self._build_hline()
460 return self._hline_string
461
462 def _build_hline(self, is_header=False):
463 """Return a string used to separated rows or separate header from
464 rows
465 """
466 horiz = self._char_horiz
467 if (is_header):
468 horiz = self._char_header
469 # compute cell separator
470 s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()],
471 horiz)
472 # build the line
473 l = s.join([horiz * n for n in self._width])
474 # add border if needed
475 if self._has_border():
476 l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz,
477 self._char_corner)
478 else:
479 l += "\n"
480 return l
481
482 def _len_cell(self, cell):
483 """Return the width of the cell
484
485 Special characters are taken into account to return the width of the
486 cell, such like newlines and tabs
487 """
488
489 cell = re.compile(r'\x1b[^m]*m').sub('', cell)
490
491 cell_lines = cell.split('\n')
492 maxi = 0
493 for line in cell_lines:
494 length = 0
495 parts = line.split('\t')
496 for part, i in zip(parts, list(range(1, len(parts) + 1))):
497 length = length + len(part)
498 if i < len(parts):
499 length = (length//8 + 1) * 8
500 maxi = max(maxi, length)
501 return maxi
502
504 """Return an array with the width of each column
505
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.
509 """
510
511 if hasattr(self, "_width"):
512 return
513 maxi = []
514 if self._header:
515 maxi = [ self._len_cell(x) for x in self._header ]
516 for row in self._rows_rows_rows:
517 for cell,i in zip(row, list(range(len(row)))):
518 try:
519 maxi[i] = max(maxi[i], self._len_cell(cell))
520 except (TypeError, IndexError):
521 maxi.append(self._len_cell(cell))
522 items = len(maxi)
523 length = reduce(lambda x,y: x+y, maxi)
524 if self._max_width and length + items * 3 + 1 > self._max_width:
525 max_lengths = maxi
526 maxi = [(self._max_width - items * 3 -1) // items \
527 for n in range(items)]
528
529 # free space to distribute
530 free = 0
531
532 # how many columns are oversized
533 oversized = 0
534
535 # reduce size of columns that need less space and calculate how
536 # much space is freed
537 for col, max_len in enumerate(max_lengths):
538 current_length = maxi[col]
539
540 # column needs less space, adjust and
541 # update free space
542 if current_length > max_len:
543 free += current_length - max_len
544 maxi[col] = max_len
545
546 # column needs more space, count it
547 elif max_len > current_length:
548 oversized += 1
549
550 # as long as free space is available, distribute it
551 while free > 0:
552 # available free space for each oversized column
553 free_part = int(math.ceil(float(free) / float(oversized)))
554
555 for col, max_len in enumerate(max_lengths):
556 current_length = maxi[col]
557
558 # column needs more space
559 if current_length < max_len:
560
561 # how much space is needed
562 needed = max_len - current_length
563
564 # enough free space for column
565 if needed <= free_part:
566 maxi[col] = max_len
567 free -= needed
568 oversized -= 1
569
570 # still oversized after re-sizing
571 else:
572 maxi[col] = maxi[col] + free_part
573 free -= free_part
574 self._width = maxi
575
576 def _check_align(self):
577 """Check if alignment has been specified, set default one if not
578 """
579
580 if not hasattr(self, "_align"):
581 self._align = ["l"] * self._row_size
582 if not hasattr(self, "_valign"):
583 self._valign = ["t"] * self._row_size
584
585 def _draw_line(self, line, isheader=False):
586 """Draw a line
587
588 Loop over a single cell length, over all the cells
589 """
590
591 line = self._splitit(line, isheader)
592 space = " "
593 out = ""
594 for i in range(len(line[0])):
595 if self._has_border():
596 out += "%s " % self._char_vert
597 length = 0
598 for cell, width, align in zip(line, self._width, self._align):
599 length += 1
600 cell_line = cell[i]
601
602 fill = width - len(re.compile(r'\x1b[^m]*m').sub('', cell_line))
603 if isheader:
604 align = "c"
605 if align == "r":
606 out += "%s " % (fill * space + cell_line)
607 elif align == "c":
608 out += "%s " % (fill//2 * space + cell_line \
609 + (fill//2 + fill%2) * space)
610 else:
611 out += "%s " % (cell_line + fill * space)
612 if length < len(line):
613 out += "%s " % [space, self._char_vert][self._has_vlines()]
614 out += "%s\n" % ['', self._char_vert][self._has_border()]
615 return out
616
617 def _splitit(self, line, isheader):
618 """Split each element of line to fit the column width
619
620 Each element is turned into a list, result of the wrapping of the
621 string to the desired width
622 """
623
624 line_wrapped = []
625 for cell, width in zip(line, self._width):
626 array = []
627 original_cell = cell
628 ansi_keep = []
629 for c in cell.split('\n'):
630 c = "".join(ansi_keep) + c
631 ansi_keep = []
632 extra_width = 0
633 for a in re.findall(r'\x1b[^m]*m', c):
634 extra_width += len(a)
635 if a == '\x1b[0m':
636 if len(ansi_keep) > 0:
637 ansi_keep.pop()
638 else:
639 ansi_keep.append(a)
640 c = c + '\x1b[0m' * len(ansi_keep)
641 extra_width += len('\x1b[0m' * len(ansi_keep))
642 if type(c) is not str:
643 try:
644 c = str(c, 'utf')
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):
652 if isheader:
653 valign = "t"
654 if valign == "m":
655 missing = max_cell_lines - len(cell)
656 cell[:0] = [""] * (missing // 2)
657 cell.extend([""] * (missing // 2 + missing % 2))
658 elif valign == "b":
659 cell[:0] = [""] * (max_cell_lines - len(cell))
660 else:
661 cell.extend([""] * (max_cell_lines - len(cell)))
662 return line_wrapped
663
664if __name__ == '__main__':
665 table = Texttable()
666 table.set_cols_align(["l", "r", "c"])
667 table.set_cols_valign(["t", "m", "b"])
668 table.add_rows([ [get_color_string(bcolors.GREEN, "Name Of Person"), "Age", get_color_string(bcolors.UNDERLINE, "Nickname")],
669 ["Mr\n" + get_color_string(bcolors.BOLD, "Xavier\nHuon"), 32, "Xav'"],
670 [get_color_string(bcolors.BLUE,get_color_string(bcolors.BOLD, "Mr\nBaptiste") + "\n" + get_color_string(bcolors.UNDERLINE, "Clement")), 1, get_color_string(bcolors.RED,"Baby")] ])
671 print(table.draw() + "\n")
672
673 table = Texttable()
674 table.set_deco(Texttable.HEADER)
675 table.set_cols_dtype(['t', # text
676 'f', # float (decimal)
677 'e', # float (exponent)
678 'i', # integer
679 'a']) # automatic
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]])
686 print(table.draw())
_draw_line(self, line, isheader=False)
_build_hline(self, is_header=False)
_splitit(self, line, isheader)
add_rows(self, rows, header=True)
get_color_string(type, string)