commit f5de339cbc8b024bb4faa0080c0d15fa53cc5cc0 Author: SVN-Git Migration Date: Thu Oct 8 14:14:35 2015 -0700 Imported Upstream version 0.8.1 diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..53bff59 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,23 @@ +Metadata-Version: 1.1 +Name: texttable +Version: 0.8.1 +Summary: module for creating simple ASCII tables +Home-page: http://foutaise.org/code/ +Author: Gerome Fournier +Author-email: jef(at)foutaise.org +License: LGPL +Download-URL: http://foutaise.org/code/texttable/texttable-0.8.1.tar.gz +Description: texttable is a module to generate a formatted text table, using ASCII + characters. +Platform: any +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing +Classifier: Topic :: Utilities diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c6fa645 --- /dev/null +++ b/setup.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# texttable - module for creating simple ASCII tables +# Copyright (C) 2003-2011 Gerome Fournier +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +DESCRIPTION = "module for creating simple ASCII tables" + +LONG_DESCRIPTION = """\ +texttable is a module to generate a formatted text table, using ASCII +characters.""" + +import sys + +from distutils.core import setup +if sys.version < '2.2.3': + from distutils.dist import DistributionMetadata + DistributionMetadata.classifiers = None + DistributionMetadata.download_url = None + +setup( + name = "texttable", + version = "0.8.1", + author = "Gerome Fournier", + author_email = "jef(at)foutaise.org", + url = "http://foutaise.org/code/", + download_url = "http://foutaise.org/code/texttable/texttable-0.8.1.tar.gz", + license = "LGPL", + py_modules = ["texttable"], + description = DESCRIPTION, + long_description = LONG_DESCRIPTION, + platforms = "any", + classifiers = [ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: MacOS', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Text Processing', + 'Topic :: Utilities', + ] +) diff --git a/texttable.py b/texttable.py new file mode 100644 index 0000000..ece839a --- /dev/null +++ b/texttable.py @@ -0,0 +1,591 @@ +#!/usr/bin/env python +# +# texttable - module for creating simple ASCII tables +# Copyright (C) 2003-2011 Gerome Fournier +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +"""module for creating simple ASCII tables + + +Example: + + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([ ["Name", "Age", "Nickname"], + ["Mr\\nXavier\\nHuon", 32, "Xav'"], + ["Mr\\nBaptiste\\nClement", 1, "Baby"] ]) + print table.draw() + "\\n" + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print table.draw() + +Result: + + +----------+-----+----------+ + | Name | Age | Nickname | + +==========+=====+==========+ + | Mr | | | + | Xavier | 32 | | + | Huon | | Xav' | + +----------+-----+----------+ + | Mr | | | + | Baptiste | 1 | | + | Clement | | Baby | + +----------+-----+----------+ + + text float exp int auto + =========================================== + abcd 67.000 6.540e+02 89 128.001 + efgh 67.543 6.540e-01 90 1.280e+22 + ijkl 0.000 5.000e-78 89 0.000 + mnop 0.023 5.000e+78 92 1.280e+22 +""" + +__all__ = ["Texttable", "ArraySizeError"] + +__author__ = 'Gerome Fournier ' +__license__ = 'LGPL' +__version__ = '0.8.1' +__credits__ = """\ +Jeff Kowalczyk: + - textwrap improved import + - comment concerning header output + +Anonymous: + - add_rows method, for adding rows in one go + +Sergey Simonenko: + - redefined len() function to deal with non-ASCII characters + +Roger Lew: + - columns datatype specifications + +Brian Peterson: + - better handling of unicode errors +""" + +import sys +import string + +try: + if sys.version >= '2.3': + import textwrap + elif sys.version >= '2.2': + from optparse import textwrap + else: + from optik import textwrap +except ImportError: + sys.stderr.write("Can't import textwrap module!\n") + raise + +try: + True, False +except NameError: + (True, False) = (1, 0) + +def len(iterable): + """Redefining len here so it will be able to work with non-ASCII characters + """ + if not isinstance(iterable, str): + return iterable.__len__() + + try: + return len(unicode(iterable, 'utf')) + except: + return iterable.__len__() + +class ArraySizeError(Exception): + """Exception raised when specified rows don't fit the required size + """ + + def __init__(self, msg): + self.msg = msg + Exception.__init__(self, msg, '') + + def __str__(self): + return self.msg + +class Texttable: + + BORDER = 1 + HEADER = 1 << 1 + HLINES = 1 << 2 + VLINES = 1 << 3 + + def __init__(self, max_width=80): + """Constructor + + - max_width is an integer, specifying the maximum width of the table + - if set to 0, size is unlimited, therefore cells won't be wrapped + """ + + if max_width <= 0: + max_width = False + self._max_width = max_width + self._precision = 3 + + self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \ + Texttable.HEADER + self.set_chars(['-', '|', '+', '=']) + self.reset() + + def reset(self): + """Reset the instance + + - reset rows and header + """ + + self._hline_string = None + self._row_size = None + self._header = [] + self._rows = [] + + def set_chars(self, array): + """Set the characters used to draw lines between rows and columns + + - the array should contain 4 fields: + + [horizontal, vertical, corner, header] + + - default is set to: + + ['-', '|', '+', '='] + """ + + if len(array) != 4: + raise ArraySizeError, "array should contain 4 characters" + array = [ x[:1] for x in [ str(s) for s in array ] ] + (self._char_horiz, self._char_vert, + self._char_corner, self._char_header) = array + + def set_deco(self, deco): + """Set the table decoration + + - 'deco' can be a combinaison of: + + Texttable.BORDER: Border around the table + Texttable.HEADER: Horizontal line below the header + Texttable.HLINES: Horizontal lines between rows + Texttable.VLINES: Vertical lines between columns + + All of them are enabled by default + + - example: + + Texttable.BORDER | Texttable.HEADER + """ + + self._deco = deco + + def set_cols_align(self, array): + """Set the desired columns alignment + + - the elements of the array should be either "l", "c" or "r": + + * "l": column flushed left + * "c": column centered + * "r": column flushed right + """ + + self._check_row_size(array) + self._align = array + + def set_cols_valign(self, array): + """Set the desired columns vertical alignment + + - the elements of the array should be either "t", "m" or "b": + + * "t": column aligned on the top of the cell + * "m": column aligned on the middle of the cell + * "b": column aligned on the bottom of the cell + """ + + self._check_row_size(array) + self._valign = array + + def set_cols_dtype(self, array): + """Set the desired columns datatype for the cols. + + - the elements of the array should be either "a", "t", "f", "e" or "i": + + * "a": automatic (try to use the most appropriate datatype) + * "t": treat as text + * "f": treat as float in decimal format + * "e": treat as float in exponential format + * "i": treat as int + + - by default, automatic datatyping is used for each column + """ + + self._check_row_size(array) + self._dtype = array + + def set_cols_width(self, array): + """Set the desired columns width + + - the elements of the array should be integers, specifying the + width of each column. For example: + + [10, 20, 5] + """ + + self._check_row_size(array) + try: + array = map(int, array) + if reduce(min, array) <= 0: + raise ValueError + except ValueError: + sys.stderr.write("Wrong argument in column width specification\n") + raise + self._width = array + + def set_precision(self, width): + """Set the desired precision for float/exponential formats + + - width must be an integer >= 0 + + - default value is set to 3 + """ + + if not type(width) is int or width < 0: + raise ValueError('width must be an integer greater then 0') + self._precision = width + + def header(self, array): + """Specify the header of the table + """ + + self._check_row_size(array) + self._header = map(str, array) + + def add_row(self, array): + """Add a row in the rows stack + + - cells can contain newlines and tabs + """ + + self._check_row_size(array) + + if not hasattr(self, "_dtype"): + self._dtype = ["a"] * self._row_size + + cells = [] + for i,x in enumerate(array): + cells.append(self._str(i,x)) + self._rows.append(cells) + + def add_rows(self, rows, header=True): + """Add several rows in the rows stack + + - The 'rows' argument can be either an iterator returning arrays, + or a by-dimensional array + - 'header' specifies if the first row should be used as the header + of the table + """ + + # nb: don't use 'iter' on by-dimensional arrays, to get a + # usable code for python 2.1 + if header: + if hasattr(rows, '__iter__') and hasattr(rows, 'next'): + self.header(rows.next()) + else: + self.header(rows[0]) + rows = rows[1:] + for row in rows: + self.add_row(row) + + def draw(self): + """Draw the table + + - the table is returned as a whole string + """ + + if not self._header and not self._rows: + return + self._compute_cols_width() + self._check_align() + out = "" + if self._has_border(): + out += self._hline() + if self._header: + out += self._draw_line(self._header, isheader=True) + if self._has_header(): + out += self._hline_header() + length = 0 + for row in self._rows: + length += 1 + out += self._draw_line(row) + if self._has_hlines() and length < len(self._rows): + out += self._hline() + if self._has_border(): + out += self._hline() + return out[:-1] + + def _str(self, i, x): + """Handles string formatting of cell data + + i - index of the cell datatype in self._dtype + x - cell data to format + """ + try: + f = float(x) + except: + return str(x) + + n = self._precision + dtype = self._dtype[i] + + if dtype == 'i': + return str(int(round(f))) + elif dtype == 'f': + return '%.*f' % (n, f) + elif dtype == 'e': + return '%.*e' % (n, f) + elif dtype == 't': + return str(x) + else: + if f - round(f) == 0: + if abs(f) > 1e8: + return '%.*e' % (n, f) + else: + return str(int(round(f))) + else: + if abs(f) > 1e8: + return '%.*e' % (n, f) + else: + return '%.*f' % (n, f) + + def _check_row_size(self, array): + """Check that the specified array fits the previous rows size + """ + + if not self._row_size: + self._row_size = len(array) + elif self._row_size != len(array): + raise ArraySizeError, "array should contain %d elements" \ + % self._row_size + + def _has_vlines(self): + """Return a boolean, if vlines are required or not + """ + + return self._deco & Texttable.VLINES > 0 + + def _has_hlines(self): + """Return a boolean, if hlines are required or not + """ + + return self._deco & Texttable.HLINES > 0 + + def _has_border(self): + """Return a boolean, if border is required or not + """ + + return self._deco & Texttable.BORDER > 0 + + def _has_header(self): + """Return a boolean, if header line is required or not + """ + + return self._deco & Texttable.HEADER > 0 + + def _hline_header(self): + """Print header's horizontal line + """ + + return self._build_hline(True) + + def _hline(self): + """Print an horizontal line + """ + + if not self._hline_string: + self._hline_string = self._build_hline() + return self._hline_string + + def _build_hline(self, is_header=False): + """Return a string used to separated rows or separate header from + rows + """ + horiz = self._char_horiz + if (is_header): + horiz = self._char_header + # compute cell separator + s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()], + horiz) + # build the line + l = string.join([horiz * n for n in self._width], s) + # add border if needed + if self._has_border(): + l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz, + self._char_corner) + else: + l += "\n" + return l + + def _len_cell(self, cell): + """Return the width of the cell + + Special characters are taken into account to return the width of the + cell, such like newlines and tabs + """ + + cell_lines = cell.split('\n') + maxi = 0 + for line in cell_lines: + length = 0 + parts = line.split('\t') + for part, i in zip(parts, range(1, len(parts) + 1)): + length = length + len(part) + if i < len(parts): + length = (length/8 + 1) * 8 + maxi = max(maxi, length) + return maxi + + def _compute_cols_width(self): + """Return an array with the width of each column + + If a specific width has been specified, exit. If the total of the + columns width exceed the table desired width, another width will be + computed to fit, and cells will be wrapped. + """ + + if hasattr(self, "_width"): + return + maxi = [] + if self._header: + maxi = [ self._len_cell(x) for x in self._header ] + for row in self._rows: + for cell,i in zip(row, range(len(row))): + try: + maxi[i] = max(maxi[i], self._len_cell(cell)) + except (TypeError, IndexError): + maxi.append(self._len_cell(cell)) + items = len(maxi) + length = reduce(lambda x,y: x+y, maxi) + if self._max_width and length + items * 3 + 1 > self._max_width: + maxi = [(self._max_width - items * 3 -1) / items \ + for n in range(items)] + self._width = maxi + + def _check_align(self): + """Check if alignment has been specified, set default one if not + """ + + if not hasattr(self, "_align"): + self._align = ["l"] * self._row_size + if not hasattr(self, "_valign"): + self._valign = ["t"] * self._row_size + + def _draw_line(self, line, isheader=False): + """Draw a line + + Loop over a single cell length, over all the cells + """ + + line = self._splitit(line, isheader) + space = " " + out = "" + for i in range(len(line[0])): + if self._has_border(): + out += "%s " % self._char_vert + length = 0 + for cell, width, align in zip(line, self._width, self._align): + length += 1 + cell_line = cell[i] + fill = width - len(cell_line) + if isheader: + align = "c" + if align == "r": + out += "%s " % (fill * space + cell_line) + elif align == "c": + out += "%s " % (fill/2 * space + cell_line \ + + (fill/2 + fill%2) * space) + else: + out += "%s " % (cell_line + fill * space) + if length < len(line): + out += "%s " % [space, self._char_vert][self._has_vlines()] + out += "%s\n" % ['', self._char_vert][self._has_border()] + return out + + def _splitit(self, line, isheader): + """Split each element of line to fit the column width + + Each element is turned into a list, result of the wrapping of the + string to the desired width + """ + + line_wrapped = [] + for cell, width in zip(line, self._width): + array = [] + for c in cell.split('\n'): + try: + c = unicode(c, 'utf') + except UnicodeDecodeError, strerror: + sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (c, strerror)) + c = unicode(c, 'utf', 'replace') + array.extend(textwrap.wrap(c, width)) + line_wrapped.append(array) + max_cell_lines = reduce(max, map(len, line_wrapped)) + for cell, valign in zip(line_wrapped, self._valign): + if isheader: + valign = "t" + if valign == "m": + missing = max_cell_lines - len(cell) + cell[:0] = [""] * (missing / 2) + cell.extend([""] * (missing / 2 + missing % 2)) + elif valign == "b": + cell[:0] = [""] * (max_cell_lines - len(cell)) + else: + cell.extend([""] * (max_cell_lines - len(cell))) + return line_wrapped + +if __name__ == '__main__': + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([ ["Name", "Age", "Nickname"], + ["Mr\nXavier\nHuon", 32, "Xav'"], + ["Mr\nBaptiste\nClement", 1, "Baby"] ]) + print table.draw() + "\n" + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print table.draw()