Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
texttable.py753 linesDownload Raw Back to site-packages
1# texttable - module to create simple ASCII tables2# Copyright (C) 2003-2023 Gerome Fournier <jef(at)foutaise.org>3 4"""module to create simple ASCII tables5 6 7Example:8 9    table = Texttable()10    table.set_cols_align(["l", "r", "c"])11    table.set_cols_valign(["t", "m", "b"])12    table.add_rows([["Name", "Age", "Nickname"],13                    ["Mr\\nXavier\\nHuon", 32, "Xav'"],14                    ["Mr\\nBaptiste\\nClement", 1, "Baby"],15                    ["Mme\\nLouise\\nBourgeau", 28, "Lou\\n\\nLoue"]])16    print(table.draw())17    print()18 19    table = Texttable()20    table.set_deco(Texttable.HEADER)21    table.set_cols_dtype(['t',  # text22                          'f',  # float (decimal)23                          'e',  # float (exponent)24                          'i',  # integer25                          'a']) # automatic26    table.set_cols_align(["l", "r", "r", "r", "l"])27    table.add_rows([["text",    "float", "exp", "int", "auto"],28                    ["abcd",    "67",    654,   89,    128.001],29                    ["efghijk", 67.5434, .654,  89.6,  12800000000000000000000.00023],30                    ["lmn",     5e-78,   5e-78, 89.4,  .000000000000128],31                    ["opqrstu", .023,    5e+78, 92.,   12800000000000000000000]])32    print(table.draw())33 34Result:35 36    +----------+-----+----------+37    |   Name   | Age | Nickname |38    +==========+=====+==========+39    | Mr       |     |          |40    | Xavier   |  32 |          |41    | Huon     |     |   Xav'   |42    +----------+-----+----------+43    | Mr       |     |          |44    | Baptiste |   1 |          |45    | Clement  |     |   Baby   |46    +----------+-----+----------+47    | Mme      |     |   Lou    |48    | Louise   |  28 |          |49    | Bourgeau |     |   Loue   |50    +----------+-----+----------+51 52    text   float       exp      int     auto53    ===========================================54    abcd   67.000   6.540e+02   89    128.00155    efgh   67.543   6.540e-01   90    1.280e+2256    ijkl   0.000    5.000e-78   89    0.00057    mnop   0.023    5.000e+78   92    1.280e+2258"""59 60from __future__ import division61 62__all__ = ["Texttable", "ArraySizeError"]63 64__author__ = 'Gerome Fournier <jef(at)foutaise.org>'65__license__ = 'MIT'66__version__ = '1.7.0'67__credits__ = """\68Jeff Kowalczyk:69    - textwrap improved import70    - comment concerning header output71 72Anonymous:73    - add_rows method, for adding rows in one go74 75Sergey Simonenko:76    - redefined len() function to deal with non-ASCII characters77 78Roger Lew:79    - columns datatype specifications80 81Brian Peterson:82    - better handling of unicode errors83 84Frank Sachsenheim:85    - add Python 2/3-compatibility86 87Maximilian Hils:88    - fix minor bug for Python 3 compatibility89 90frinkelpi:91    - preserve empty lines92"""93 94import sys95import unicodedata96 97# define a text wrapping function to wrap some text98# to a specific width:99# - use cjkwrap if available (better CJK support)100# - fallback to textwrap otherwise101try:102    import cjkwrap103    def textwrapper(txt, width):104        return cjkwrap.wrap(txt, width)105except ImportError:106    try:107        import textwrap108        def textwrapper(txt, width):109            return textwrap.wrap(txt, width)110    except ImportError:111        sys.stderr.write("Can't import textwrap module!\n")112        raise113 114# define a function to calculate the rendering width of a unicode character115# - use wcwidth if available116# - fallback to unicodedata information otherwise117try:118    import wcwidth119    def uchar_width(c):120        """Return the rendering width of a unicode character121        """122        return max(0, wcwidth.wcwidth(c))123except ImportError:124    def uchar_width(c):125        """Return the rendering width of a unicode character126        """127        if unicodedata.east_asian_width(c) in 'WF':128            return 2129        elif unicodedata.combining(c):130            return 0131        else:132            return 1133 134from functools import reduce135 136if sys.version_info >= (3, 0):137    unicode_type = str138    bytes_type = bytes139else:140    unicode_type = unicode141    bytes_type = str142 143 144def obj2unicode(obj):145    """Return a unicode representation of a python object146    """147    if isinstance(obj, unicode_type):148        return obj149    elif isinstance(obj, bytes_type):150        try:151            return unicode_type(obj, 'utf-8')152        except UnicodeDecodeError as strerror:153            sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (obj, strerror))154            return unicode_type(obj, 'utf-8', 'replace')155    else:156        return unicode_type(obj)157 158 159def len(iterable):160    """Redefining len here so it will be able to work with non-ASCII characters161    """162    if isinstance(iterable, bytes_type) or isinstance(iterable, unicode_type):163        return sum([uchar_width(c) for c in obj2unicode(iterable)])164    else:165        return iterable.__len__()166 167 168class ArraySizeError(Exception):169    """Exception raised when specified rows don't fit the required size170    """171 172    def __init__(self, msg):173        self.msg = msg174        Exception.__init__(self, msg, '')175 176    def __str__(self):177        return self.msg178 179 180class FallbackToText(Exception):181    """Used for failed conversion to float"""182    pass183 184 185class Texttable:186 187    BORDER = 1188    HEADER = 1 << 1189    HLINES = 1 << 2190    VLINES = 1 << 3191 192    def __init__(self, max_width=80):193        """Constructor194 195        - max_width is an integer, specifying the maximum width of the table196        - if set to 0, size is unlimited, therefore cells won't be wrapped197        """198 199        self.set_max_width(max_width)200        self._precision = 3201 202        self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \203            Texttable.HEADER204        self.set_chars(['-', '|', '+', '='])205        self.reset()206 207    def reset(self):208        """Reset the instance209 210        - reset rows and header211        """212 213        self._hline_string = None214        self._row_size = None215        self._header = []216        self._rows = []217        return self218 219    def set_max_width(self, max_width):220        """Set the maximum width of the table221 222        - max_width is an integer, specifying the maximum width of the table223        - if set to 0, size is unlimited, therefore cells won't be wrapped224        """225        self._max_width = max_width if max_width > 0 else False226        return self227 228    def set_chars(self, array):229        """Set the characters used to draw lines between rows and columns230 231        - the array should contain 4 fields:232 233            [horizontal, vertical, corner, header]234 235        - default is set to:236 237            ['-', '|', '+', '=']238        """239 240        if len(array) != 4:241            raise ArraySizeError("array should contain 4 characters")242        array = [ x[:1] for x in [ str(s) for s in array ] ]243        (self._char_horiz, self._char_vert,244            self._char_corner, self._char_header) = array245        return self246 247    def set_deco(self, deco):248        """Set the table decoration249 250        - 'deco' can be a combination of:251 252            Texttable.BORDER: Border around the table253            Texttable.HEADER: Horizontal line below the header254            Texttable.HLINES: Horizontal lines between rows255            Texttable.VLINES: Vertical lines between columns256 257           All of them are enabled by default258 259        - example:260 261            Texttable.BORDER | Texttable.HEADER262        """263 264        self._deco = deco265        self._hline_string = None266        return self267 268    def set_header_align(self, array):269        """Set the desired header alignment270 271        - the elements of the array should be either "l", "c" or "r":272 273            * "l": column flushed left274            * "c": column centered275            * "r": column flushed right276        """277 278        self._check_row_size(array)279        self._header_align = array280        return self281 282    def set_cols_align(self, array):283        """Set the desired columns alignment284 285        - the elements of the array should be either "l", "c" or "r":286 287            * "l": column flushed left288            * "c": column centered289            * "r": column flushed right290        """291 292        self._check_row_size(array)293        self._align = array294        return self295 296    def set_cols_valign(self, array):297        """Set the desired columns vertical alignment298 299        - the elements of the array should be either "t", "m" or "b":300 301            * "t": column aligned on the top of the cell302            * "m": column aligned on the middle of the cell303            * "b": column aligned on the bottom of the cell304        """305 306        self._check_row_size(array)307        self._valign = array308        return self309 310    def set_cols_dtype(self, array):311        """Set the desired columns datatype for the cols.312 313        - the elements of the array should be either a callable or any of314          "a", "t", "f", "e", "i" or "b":315 316            * "a": automatic (try to use the most appropriate datatype)317            * "t": treat as text318            * "f": treat as float in decimal format319            * "e": treat as float in exponential format320            * "i": treat as int321            * "b": treat as boolean322            * a callable: should return formatted string for any value given323 324        - by default, automatic datatyping is used for each column325        """326 327        self._check_row_size(array)328        self._dtype = array329        return self330 331    def set_cols_width(self, array):332        """Set the desired columns width333 334        - the elements of the array should be integers, specifying the335          width of each column. For example:336 337                [10, 20, 5]338        """339 340        self._check_row_size(array)341        try:342            array = list(map(int, array))343            if reduce(min, array) <= 0:344                raise ValueError345        except ValueError:346            sys.stderr.write("Wrong argument in column width specification\n")347            raise348        self._width = array349        return self350 351    def set_precision(self, width):352        """Set the desired precision for float/exponential formats353 354        - width must be an integer >= 0355 356        - default value is set to 3357        """358 359        if not type(width) is int or width < 0:360            raise ValueError('width must be an integer greater then 0')361        self._precision = width362        return self363 364    def header(self, array):365        """Specify the header of the table366        """367 368        self._check_row_size(array)369        self._header = list(map(obj2unicode, array))370        return self371 372    def add_row(self, array):373        """Add a row in the rows stack374 375        - cells can contain newlines and tabs376        """377 378        self._check_row_size(array)379 380        if not hasattr(self, "_dtype"):381            self._dtype = ["a"] * self._row_size382 383        cells = []384        for i, x in enumerate(array):385            cells.append(self._str(i, x))386        self._rows.append(cells)387        return self388 389    def add_rows(self, rows, header=True):390        """Add several rows in the rows stack391 392        - The 'rows' argument can be either an iterator returning arrays,393          or a by-dimensional array394        - 'header' specifies if the first row should be used as the header395          of the table396        """397 398        # nb: don't use 'iter' on by-dimensional arrays, to get a399        #     usable code for python 2.1400        if header:401            if hasattr(rows, '__iter__') and hasattr(rows, 'next'):402                self.header(rows.next())403            else:404                self.header(rows[0])405                rows = rows[1:]406        for row in rows:407            self.add_row(row)408        return self409 410    def draw(self):411        """Draw the table412 413        - the table is returned as a whole string414        """415 416        if not self._header and not self._rows:417            return418        self._compute_cols_width()419        self._check_align()420        out = ""421        if self._has_border():422            out += self._hline()423        if self._header:424            out += self._draw_line(self._header, isheader=True)425            if self._has_header():426                out += self._hline_header()427        length = 0428        for row in self._rows:429            length += 1430            out += self._draw_line(row)431            if self._has_hlines() and length < len(self._rows):432                out += self._hline()433        if self._has_border():434            out += self._hline()435        return out[:-1]436 437    @classmethod438    def _to_float(cls, x):439        if x is None:440            raise FallbackToText()441        try:442            return float(x)443        except (TypeError, ValueError):444            raise FallbackToText()445 446    @classmethod447    def _fmt_int(cls, x, **kw):448        """Integer formatting class-method.449        """450        if type(x) == int:451            return str(x)452        else:453            return str(int(round(cls._to_float(x))))454 455    @classmethod456    def _fmt_float(cls, x, **kw):457        """Float formatting class-method.458 459        - x parameter is ignored. Instead kw-argument f being x float-converted460          will be used.461 462        - precision will be taken from `n` kw-argument.463        """464        n = kw.get('n')465        return '%.*f' % (n, cls._to_float(x))466 467    @classmethod468    def _fmt_exp(cls, x, **kw):469        """Exponential formatting class-method.470 471        - x parameter is ignored. Instead kw-argument f being x float-converted472          will be used.473 474        - precision will be taken from `n` kw-argument.475        """476        n = kw.get('n')477        return '%.*e' % (n, cls._to_float(x))478 479    @classmethod480    def _fmt_text(cls, x, **kw):481        """String formatting class-method."""482        return obj2unicode(x)483 484    @classmethod485    def _fmt_bool(cls, x, **kw):486        """Boolean formatting class-method"""487        return str(bool(x))488 489    @classmethod490    def _fmt_auto(cls, x, **kw):491        """auto formatting class-method."""492        f = cls._to_float(x)493        if abs(f) > 1e8:494            fn = cls._fmt_exp495        elif f != f:  # NaN496            fn = cls._fmt_text497        elif f - round(f) == 0:498            fn = cls._fmt_bool if isinstance(x, bool) else cls._fmt_int499        else:500            fn = cls._fmt_float501        return fn(x, **kw)502 503    def _str(self, i, x):504        """Handles string formatting of cell data505 506            i - index of the cell datatype in self._dtype507            x - cell data to format508        """509        FMT = {510            'a':self._fmt_auto,511            'i':self._fmt_int,512            'b':self._fmt_bool,513            'f':self._fmt_float,514            'e':self._fmt_exp,515            't':self._fmt_text,516            }517 518        n = self._precision519        dtype = self._dtype[i]520        try:521            if callable(dtype):522                return dtype(x)523            else:524                return FMT[dtype](x, n=n)525        except FallbackToText:526            return self._fmt_text(x)527 528    def _check_row_size(self, array):529        """Check that the specified array fits the previous rows size530        """531 532        if not self._row_size:533            self._row_size = len(array)534        elif self._row_size != len(array):535            raise ArraySizeError("array should contain %d elements" \536                % self._row_size)537 538    def _has_vlines(self):539        """Return a boolean, if vlines are required or not540        """541 542        return self._deco & Texttable.VLINES > 0543 544    def _has_hlines(self):545        """Return a boolean, if hlines are required or not546        """547 548        return self._deco & Texttable.HLINES > 0549 550    def _has_border(self):551        """Return a boolean, if border is required or not552        """553 554        return self._deco & Texttable.BORDER > 0555 556    def _has_header(self):557        """Return a boolean, if header line is required or not558        """559 560        return self._deco & Texttable.HEADER > 0561 562    def _hline_header(self):563        """Print header's horizontal line564        """565 566        return self._build_hline(True)567 568    def _hline(self):569        """Print an horizontal line570        """571 572        if not self._hline_string:573            self._hline_string = self._build_hline()574        return self._hline_string575 576    def _build_hline(self, is_header=False):577        """Return a string used to separated rows or separate header from578        rows579        """580        horiz = self._char_horiz581        if (is_header):582            horiz = self._char_header583        # compute cell separator584        s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()],585            horiz)586        # build the line587        l = s.join([horiz * n for n in self._width])588        # add border if needed589        if self._has_border():590            l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz,591                self._char_corner)592        else:593            l += "\n"594        return l595 596    def _len_cell(self, cell):597        """Return the width of the cell598 599        Special characters are taken into account to return the width of the600        cell, such like newlines and tabs601        """602 603        cell_lines = cell.split('\n')604        maxi = 0605        for line in cell_lines:606            length = 0607            parts = line.split('\t')608            for part, i in zip(parts, list(range(1, len(parts) + 1))):609                length = length + len(part)610                if i < len(parts):611                    length = (length//8 + 1) * 8612            maxi = max(maxi, length)613        return maxi614 615    def _compute_cols_width(self):616        """Return an array with the width of each column617 618        If a specific width has been specified, exit. If the total of the619        columns width exceed the table desired width, another width will be620        computed to fit, and cells will be wrapped.621        """622 623        if hasattr(self, "_width"):624            return625        maxi = []626        if self._header:627            maxi = [ self._len_cell(x) for x in self._header ]628        for row in self._rows:629            for cell,i in zip(row, list(range(len(row)))):630                try:631                    maxi[i] = max(maxi[i], self._len_cell(cell))632                except (TypeError, IndexError):633                    maxi.append(self._len_cell(cell))634 635        ncols = len(maxi)636        content_width = sum(maxi)637        deco_width = 3*(ncols-1) + [0,4][self._has_border()]638        if self._max_width and (content_width + deco_width) > self._max_width:639            """ content too wide to fit the expected max_width640            let's recompute maximum cell width for each cell641            """642            if self._max_width < (ncols + deco_width):643                raise ValueError('max_width too low to render data')644            available_width = self._max_width - deco_width645            newmaxi = [0] * ncols646            i = 0647            while available_width > 0:648                if newmaxi[i] < maxi[i]:649                    newmaxi[i] += 1650                    available_width -= 1651                i = (i + 1) % ncols652            maxi = newmaxi653        self._width = maxi654 655    def _check_align(self):656        """Check if alignment has been specified, set default one if not657        """658 659        if not hasattr(self, "_header_align"):660            self._header_align = ["c"] * self._row_size661        if not hasattr(self, "_align"):662            self._align = ["l"] * self._row_size663        if not hasattr(self, "_valign"):664            self._valign = ["t"] * self._row_size665 666    def _draw_line(self, line, isheader=False):667        """Draw a line668 669        Loop over a single cell length, over all the cells670        """671 672        line = self._splitit(line, isheader)673        space = " "674        out = ""675        for i in range(len(line[0])):676            if self._has_border():677                out += "%s " % self._char_vert678            length = 0679            for cell, width, align in zip(line, self._width, self._align):680                length += 1681                cell_line = cell[i]682                fill = width - len(cell_line)683                if isheader:684                    align = self._header_align[length - 1]685                if align == "r":686                    out += fill * space + cell_line687                elif align == "c":688                    out += (int(fill/2) * space + cell_line \689                            + int(fill/2 + fill%2) * space)690                else:691                    out += cell_line + fill * space692                if length < len(line):693                    out += " %s " % [space, self._char_vert][self._has_vlines()]694            out += "%s\n" % ['', space + self._char_vert][self._has_border()]695        return out696 697    def _splitit(self, line, isheader):698        """Split each element of line to fit the column width699 700        Each element is turned into a list, result of the wrapping of the701        string to the desired width702        """703 704        line_wrapped = []705        for cell, width in zip(line, self._width):706            array = []707            for c in cell.split('\n'):708                if c.strip() == "":709                    array.append("")710                else:711                    array.extend(textwrapper(c, width))712            line_wrapped.append(array)713        max_cell_lines = reduce(max, list(map(len, line_wrapped)))714        for cell, valign in zip(line_wrapped, self._valign):715            if isheader:716                valign = "t"717            if valign == "m":718                missing = max_cell_lines - len(cell)719                cell[:0] = [""] * int(missing / 2)720                cell.extend([""] * int(missing / 2 + missing % 2))721            elif valign == "b":722                cell[:0] = [""] * (max_cell_lines - len(cell))723            else:724                cell.extend([""] * (max_cell_lines - len(cell)))725        return line_wrapped726 727 728if __name__ == '__main__':729    table = Texttable()730    table.set_cols_align(["l", "r", "c"])731    table.set_cols_valign(["t", "m", "b"])732    table.add_rows([["Name", "Age", "Nickname"],733                    ["Mr\nXavier\nHuon", 32, "Xav'"],734                    ["Mr\nBaptiste\nClement", 1, "Baby"],735                    ["Mme\nLouise\nBourgeau", 28, "Lou\n \nLoue"]])736    print(table.draw())737    print()738 739    table = Texttable()740    table.set_deco(Texttable.HEADER)741    table.set_cols_dtype(['t',  # text742                          'f',  # float (decimal)743                          'e',  # float (exponent)744                          'i',  # integer745                          'a']) # automatic746    table.set_cols_align(["l", "r", "r", "r", "l"])747    table.add_rows([["text",    "float", "exp", "int", "auto"],748                    ["abcd",    "67",    654,   89,    128.001],749                    ["efghijk", 67.5434, .654,  89.6,  12800000000000000000000.00023],750                    ["lmn",     5e-78,   5e-78, 89.4,  .000000000000128],751                    ["opqrstu", .023,    5e+78, 92.,   12800000000000000000000]])752    print(table.draw())753 
codekingpro/portable-devtools · Team Ai