pax_global_header00006660000000000000000000000064140733455230014520gustar00rootroot0000000000000052 comment=98657cc2a25b6ba2abe6ee86a50ad3ef90f403b6 texttable-1.6.4/000077500000000000000000000000001407334552300135245ustar00rootroot00000000000000texttable-1.6.4/.coveragerc000066400000000000000000000004101407334552300156400ustar00rootroot00000000000000# .coveragerc to control coverage.py [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma: pragma: no cover # Don't complain if non-runnable code isn't run: if __name__ == .__main__.: texttable-1.6.4/.gitignore000066400000000000000000000001051407334552300155100ustar00rootroot00000000000000/.cache/ /.coverage /.tox/ /MANIFEST /__pycache__/ *.egg-info/ *.pyc texttable-1.6.4/CHANGELOG.md000066400000000000000000000051041407334552300153350ustar00rootroot00000000000000# Version History v1.6.4 (2021-07-13) * Fix alignment bug when deco is modified (https://github.com/foutaise/texttable/issues/76) v1.6.3 (2020-09-06) * Improve int conversion (https://github.com/foutaise/texttable/issues/70) v1.6.2 (2019-07-01) * Fix auto-formatting NaN (https://github.com/foutaise/texttable/pull/60) v1.6.1 (2019-02-15) * Include tests, license in source tarball (https://github.com/foutaise/texttable/issues/58) * Add changelog v1.6.0 (2019-01-17) * Add basic emoji support (https://github.com/foutaise/texttable/issues/55) v1.5.0 (2018-11-02) * Create a method for redefining the max_width (https://github.com/foutaise/texttable/issues/54) * Use setuptools instead of distutils to upload metadata to PyPI (https://github.com/foutaise/texttable/issues/49) * Switch to MIT license v1.4.0 (2018-06-22) * Add set_header_align() method (https://github.com/foutaise/texttable/issues/45) v1.3.1 (2018-06-12) * Fix missing textwrapper command when cjkwrap is not used (https://github.com/foutaise/texttable/issues/43) v1.3.0 (2018-06-11) * Remove redundant code for unsupported/EOL Python (https://github.com/foutaise/texttable/pull/31) v1.2.1 (2018-01-03) * Use test_cjkwrap only when cjkwrap is available (https://github.com/foutaise/texttable/issues/35) v1.2.0 (2018-01-03) * Use cjkwrap for better CJK text support (https://github.com/foutaise/texttable/issues/34) v1.1.1 (2017-10-26) * Fallback to text on TypeError (https://github.com/foutaise/texttable/issues/28) v1.1.0 (2017-10-22) * Easier formatting, allow callable as a column datatype (PR https://github.com/foutaise/texttable/pull/27) v1.0.0 (2017-10-14) * Fix bug in wide chars handling (https://github.com/foutaise/texttable/issues/9) * Avoid use of sys.version to obtain Python version (https://github.com/foutaise/texttable/pull/24) v0.9.1 (2017-06-27) * Add support for combining characters (https://github.com/foutaise/texttable/pull/19) v0.9.0 (2017-05-16) * Fix width of table exceeds max_width parameter (https://github.com/foutaise/texttable/pull/15) v0.8.8 (2017-03-30) * Add east asian support (https://github.com/foutaise/texttable/pull/12) * Relative col widths improvements + unit tests (https://github.com/foutaise/texttable/pull/13) v0.8.7 (2016-11-14) * Proper handling of unicode in headers (https://github.com/foutaise/texttable/issues/9) v0.8.6 (2016-10-21) * Preserve empty lines (https://github.com/foutaise/texttable/pull/8) v0.8.5 (2016-10-16) * Better handling of unicode encodings (https://github.com/foutaise/texttable/pull/6) v0.8.4 (2015-11-16) * Fix pypi url v0.8.3 (2015-11-16) * Update README.md texttable-1.6.4/LICENSE000066400000000000000000000020731407334552300145330ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2020 Gerome Fournier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. texttable-1.6.4/MANIFEST.in000066400000000000000000000001621407334552300152610ustar00rootroot00000000000000include *.md include *.py include *.pyi include LICENSE include tox.ini include .coveragerc global-exclude *.pyc texttable-1.6.4/PKG-INFO000066400000000000000000000025241407334552300146240ustar00rootroot00000000000000Metadata-Version: 1.0 Name: texttable Version: 1.6.4 Summary: module for creating simple ASCII tables Home-page: https://github.com/foutaise/texttable/ Author: Gerome Fournier Author-email: jef@foutaise.org License: MIT Download-URL: https://github.com/foutaise/texttable/archive/v1.6.4.tar.gz Description: texttable is a module to generate a formatted text table, using ASCII characters. Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Text Processing Classifier: Topic :: Utilities texttable-1.6.4/README.md000066400000000000000000000203761407334552300150130ustar00rootroot00000000000000# texttable Python module for creating simple ASCII tables ## Availability This module is available on [PyPI](https://pypi.org/project/texttable/), and has been packaged for several Linux/Unix platforms ([Debian](https://packages.debian.org/search?&searchon=names&keywords=python-texttable+), [FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=texttable&stype=all), Fedora, Suse...). ## Dependencies If available, [cjkwrap](https://github.com/fgallaire/cjkwrap) library is used instead of textwrap, for a better wrapping of CJK text. If available, [wcwidth](https://github.com/jquast/wcwidth) library is used for a better rendering (basic emoji support). ## Documentation ``` NAME texttable - module for creating simple ASCII tables FILE /usr/local/lib/python2.7/dist-packages/texttable.py DESCRIPTION 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"], ["Mme\nLouise\nBourgeau", 28, "Lou\n\nLoue"]]) print(table.draw()) print() 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 | +----------+-----+----------+ | Mme | | Lou | | Louise | 28 | | | Bourgeau | | Loue | +----------+-----+----------+ text float exp int auto ============================================== abcd 67.000 6.540e+02 89 128.001 efghijk 67.543 6.540e-01 90 1.280e+22 lmn 0.000 5.000e-78 89 0.000 opqrstu 0.023 5.000e+78 92 1.280e+22 CLASSES class Texttable | Methods defined here: | | __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 | | add_row(self, array) | Add a row in the rows stack | | - cells can contain newlines and tabs | | 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 | | draw(self) | Draw the table | | - the table is returned as a whole string | | header(self, array) | Specify the header of the table | | reset(self) | Reset the instance | | - reset rows and header | | 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: | | ['-', '|', '+', '='] | | 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 | | set_cols_dtype(self, array) | Set the desired columns datatype for the cols. | | - the elements of the array should be either a callable or any of | "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 | * a callable: should return formatted string for any value given | | - by default, automatic datatyping is used for each column | | 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 | | 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] | | set_deco(self, deco) | Set the table decoration | | - 'deco' can be a combination 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 | | set_header_align(self, array) | Set the desired header alignment | | - the elements of the array should be either "l", "c" or "r": | | * "l": column flushed left | * "c": column centered | * "r": column flushed right | | set_max_width(self, max_width) | Set the maximum width of the table | | - 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 | | set_precision(self, width) | Set the desired precision for float/exponential formats | | - width must be an integer >= 0 | | - default value is set to 3 | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | BORDER = 1 | | HEADER = 2 | | HLINES = 4 | | VLINES = 8 DATA __all__ = ['Texttable', 'ArraySizeError'] __author__ = 'Gerome Fournier ' __credits__ = 'Jeff Kowalczyk:\n - textwrap improved import\n ...at... __license__ = 'MIT' __version__ = '1.6.4' VERSION 1.6.4 AUTHOR Gerome Fournier 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 Frank Sachsenheim: - add Python 2/3-compatibility Maximilian Hils: - fix minor bug for Python 3 compatibility frinkelpi: - preserve empty lines ``` ## Forks * [latextable](https://github.com/JAEarly/latextable) is a fork of texttable that provide a LaTeX backend. texttable-1.6.4/setup.py000066400000000000000000000033011407334552300152330ustar00rootroot00000000000000#!/usr/bin/env python # # texttable - module for creating simple ASCII tables # Copyright (C) 2003-2020 Gerome Fournier from setuptools import setup DESCRIPTION = "module for creating simple ASCII tables" with open("README.md") as f: LONG_DESCRIPTION = f.read() setup( name="texttable", version="1.6.4", author="Gerome Fournier", author_email="jef@foutaise.org", url="https://github.com/foutaise/texttable/", download_url="https://github.com/foutaise/texttable/archive/v1.6.4.tar.gz", license="MIT", py_modules=["texttable"], description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type="text/markdown", platforms="any", classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: MIT License', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: MacOS', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Processing', 'Topic :: Utilities', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', ], options={"bdist_wheel": {"universal": "1"}} ) texttable-1.6.4/tests.py000066400000000000000000000214071407334552300152440ustar00rootroot00000000000000#coding: utf-8 import re import sys from textwrap import dedent from texttable import Texttable if sys.version >= '3': u_dedent = dedent else: def u_dedent(b): return unicode(dedent(b), 'utf-8') def clean(text): return re.sub(r'( +)$', '', text, flags=re.MULTILINE) + '\n' def test_texttable(): 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"], ["Mme\nLouise\nBourgeau", 28, "Lou\n \nLoue"], ]) assert clean(table.draw()) == dedent('''\ +----------+-----+----------+ | Name | Age | Nickname | +==========+=====+==========+ | Mr | | | | Xavier | 32 | | | Huon | | Xav' | +----------+-----+----------+ | Mr | | | | Baptiste | 1 | | | Clement | | Baby | +----------+-----+----------+ | Mme | | Lou | | Louise | 28 | | | Bourgeau | | Loue | +----------+-----+----------+ ''') def test_texttable_header(): 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], ]) assert clean(table.draw()) == dedent('''\ text float exp int auto ============================================== abcd 67.000 6.540e+02 89 128.001 efghijk 67.543 6.540e-01 90 1.280e+22 lmn 0.000 5.000e-78 89 0.000 opqrstu 0.023 5.000e+78 92 1.280e+22 ''') def test_set_cols_width(): table = Texttable() table.set_deco(Texttable.HEADER) table.set_cols_width([10, 10]) table.add_rows([ ["key", "value"], [1, "a"], [2, "b"], ]) assert clean(table.draw()) == dedent('''\ key value ======================= 1 a 2 b ''') def test_exceeding_max_width(): table = Texttable(max_width=35) table.set_deco(Texttable.HEADER) table.add_rows([ ["key", "value"], [1, "a"], [2, "b"], [3, "very long, very long, very long"], ]) assert clean(table.draw()) == dedent('''\ key value =================================== 1 a 2 b 3 very long, very long, very long ''') def test_exceeding_max_width2(): table = Texttable(max_width=14) table.add_rows([ ["a", "b"], [1, "+"], [22, "++++++++"], ]) assert clean(table.draw()) == dedent('''\ +----+-------+ | a | b | +====+=======+ | 1 | + | +----+-------+ | 22 | +++++ | | | +++ | +----+-------+ ''') def test_exceeding_max_width3(): table = Texttable() table.set_max_width(35) table.set_deco(Texttable.HEADER) table.add_rows([ ["key", "value"], [1, "a"], [2, "b"], [3, "very long, very long, very long"], ]) assert clean(table.draw()) == dedent('''\ key value =================================== 1 a 2 b 3 very long, very long, very long ''') def test_exceeding_max_width4(): table = Texttable() table.set_max_width(14) table.add_rows([ ["a", "b"], [1, "+"], [22, "++++++++"], ]) assert clean(table.draw()) == dedent('''\ +----+-------+ | a | b | +====+=======+ | 1 | + | +----+-------+ | 22 | +++++ | | | +++ | +----+-------+ ''') def test_obj2unicode(): table = Texttable() table.set_deco(Texttable.HEADER) table.add_rows([ ["key", "value"], [1, "a"], [2, 1], [3, None], ]) assert clean(table.draw()) == dedent('''\ key value =========== 1 a 2 1 3 None ''') def test_combining_char(): table = Texttable() table.set_cols_align(["l", "r", "r"]) table.add_rows([ ["str", "code-point\nlength", "display\nwidth"], ["ā", 2, 1], ["a", 1, 1], ]) assert clean(table.draw()) == u_dedent('''\ +-----+------------+---------+ | str | code-point | display | | | length | width | +=====+============+=========+ | ā | 2 | 1 | +-----+------------+---------+ | a | 1 | 1 | +-----+------------+---------+ ''') def test_combining_char2(): table = Texttable() table.add_rows([ ["a", "b", "c"], ["诶诶诶", "bbb", "西西西"], ], False) assert clean(table.draw()) == u_dedent('''\ +--------+-----+--------+ | a | b | c | +--------+-----+--------+ | 诶诶诶 | bbb | 西西西 | +--------+-----+--------+ ''') def test_user_dtype(): table = Texttable() table.set_cols_align(["l", "r", "r"]) table.set_cols_dtype([ 'a', # automatic lambda s:str(s)+"s", # user-def lambda s:('%s'%s) if s>=0 else '[%s]'%(-s), # user-def ]) table.add_rows([ ["str", "code-point\nlength", "display\nwidth"], ["a", 2, 1], ["a", 1,-3], ]) assert clean(table.draw()) == u_dedent('''\ +-----+------------+---------+ | str | code-point | display | | | length | width | +=====+============+=========+ | a | 2s | 1 | +-----+------------+---------+ | a | 1s | [3] | +-----+------------+---------+ ''') def test_cjkwarp(): try: import cjkwrap table = Texttable() table.set_cols_align(["r", "l"]) table.add_rows([ ["Name", 'Discuz! 6.x/7.x 全局变量防御绕过导致命令执行'], ["Description", '由于php5.3.x版本里php.ini的设置里request_order默认值为GP,导致Discuz! 6.x/7.x 全局变量防御绕过漏洞'], ], header = False) assert clean(table.draw()) == u_dedent('''\ +-------------+----------------------------------------------------------------+ | Name | Discuz! 6.x/7.x 全局变量防御绕过导致命令执行 | +-------------+----------------------------------------------------------------+ | Description | 由于php5.3.x版本里php.ini的设置里request_order默认值为GP,导致 | | | Discuz! 6.x/7.x 全局变量防御绕过漏洞 | +-------------+----------------------------------------------------------------+ ''') except ImportError: True def test_chaining(): table = Texttable() table.reset() table.set_max_width(50) table.set_chars(list('-|+=')) table.set_deco(Texttable.BORDER) table.set_header_align(list('lll')) table.set_cols_align(list('lll')) table.set_cols_valign(list('mmm')) table.set_cols_dtype(list('ttt')) table.set_cols_width([3, 3, 3]) table.set_precision(3) table.header(list('abc')) table.add_row(list('def')) table.add_rows([list('ghi')], False) s1 = table.draw() s2 = (Texttable() .reset() .set_max_width(50) .set_chars(list('-|+=')) .set_deco(Texttable.BORDER) .set_header_align(list('lll')) .set_cols_align(list('lll')) .set_cols_valign(list('mmm')) .set_cols_dtype(list('ttt')) .set_cols_width([3, 3, 3]) .set_precision(3) .header(list('abc')) .add_row(list('def')) .add_rows([list('ghi')], False) .draw()) assert s1 == s2 def test_nan(): table = Texttable() table.set_cols_align(["l"]) table.add_rows([ ["A NaN"], ["NaN"], ]) assert clean(table.draw()) == u_dedent('''\ +-------+ | A NaN | +=======+ | NaN | +-------+ ''') texttable-1.6.4/texttable.py000066400000000000000000000541311407334552300160760ustar00rootroot00000000000000# texttable - module for creating simple ASCII tables # Copyright (C) 2003-2020 Gerome Fournier """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"], ["Mme\\nLouise\\nBourgeau", 28, "Lou\\n\\nLoue"]]) print(table.draw()) print() 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 | +----------+-----+----------+ | Mme | | Lou | | Louise | 28 | | | Bourgeau | | Loue | +----------+-----+----------+ 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 """ from __future__ import division __all__ = ["Texttable", "ArraySizeError"] __author__ = 'Gerome Fournier ' __license__ = 'MIT' __version__ = '1.6.4' __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 Frank Sachsenheim: - add Python 2/3-compatibility Maximilian Hils: - fix minor bug for Python 3 compatibility frinkelpi: - preserve empty lines """ import sys import unicodedata # define a text wrapping function to wrap some text # to a specific width: # - use cjkwrap if available (better CJK support) # - fallback to textwrap otherwise try: import cjkwrap def textwrapper(txt, width): return cjkwrap.wrap(txt, width) except ImportError: try: import textwrap def textwrapper(txt, width): return textwrap.wrap(txt, width) except ImportError: sys.stderr.write("Can't import textwrap module!\n") raise # define a function to calculate the rendering width of a unicode character # - use wcwidth if available # - fallback to unicodedata information otherwise try: import wcwidth def uchar_width(c): """Return the rendering width of a unicode character """ return max(0, wcwidth.wcwidth(c)) except ImportError: def uchar_width(c): """Return the rendering width of a unicode character """ if unicodedata.east_asian_width(c) in 'WF': return 2 elif unicodedata.combining(c): return 0 else: return 1 from functools import reduce if sys.version_info >= (3, 0): unicode_type = str bytes_type = bytes else: unicode_type = unicode bytes_type = str def obj2unicode(obj): """Return a unicode representation of a python object """ if isinstance(obj, unicode_type): return obj elif isinstance(obj, bytes_type): try: return unicode_type(obj, 'utf-8') except UnicodeDecodeError as strerror: sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (obj, strerror)) return unicode_type(obj, 'utf-8', 'replace') else: return unicode_type(obj) def len(iterable): """Redefining len here so it will be able to work with non-ASCII characters """ if isinstance(iterable, bytes_type) or isinstance(iterable, unicode_type): return sum([uchar_width(c) for c in obj2unicode(iterable)]) else: 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 FallbackToText(Exception): """Used for failed conversion to float""" pass 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 """ self.set_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 = [] return self def set_max_width(self, max_width): """Set the maximum width of the table - 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 """ self._max_width = max_width if max_width > 0 else False return self 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 return self def set_deco(self, deco): """Set the table decoration - 'deco' can be a combination 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 self._hline_string = None return self def set_header_align(self, array): """Set the desired header 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._header_align = array return self 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 return self 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 return self def set_cols_dtype(self, array): """Set the desired columns datatype for the cols. - the elements of the array should be either a callable or any of "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 * a callable: should return formatted string for any value given - by default, automatic datatyping is used for each column """ self._check_row_size(array) self._dtype = array return self 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 = list(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 return self 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 return self def header(self, array): """Specify the header of the table """ self._check_row_size(array) self._header = list(map(obj2unicode, array)) return self 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) return self 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) return self 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] @classmethod def _to_float(cls, x): if x is None: raise FallbackToText() try: return float(x) except (TypeError, ValueError): raise FallbackToText() @classmethod def _fmt_int(cls, x, **kw): """Integer formatting class-method. """ if type(x) == int: return str(x) else: return str(int(round(cls._to_float(x)))) @classmethod def _fmt_float(cls, x, **kw): """Float formatting class-method. - x parameter is ignored. Instead kw-argument f being x float-converted will be used. - precision will be taken from `n` kw-argument. """ n = kw.get('n') return '%.*f' % (n, cls._to_float(x)) @classmethod def _fmt_exp(cls, x, **kw): """Exponential formatting class-method. - x parameter is ignored. Instead kw-argument f being x float-converted will be used. - precision will be taken from `n` kw-argument. """ n = kw.get('n') return '%.*e' % (n, cls._to_float(x)) @classmethod def _fmt_text(cls, x, **kw): """String formatting class-method.""" return obj2unicode(x) @classmethod def _fmt_auto(cls, x, **kw): """auto formatting class-method.""" f = cls._to_float(x) if abs(f) > 1e8: fn = cls._fmt_exp elif f != f: # NaN fn = cls._fmt_text elif f - round(f) == 0: fn = cls._fmt_int else: fn = cls._fmt_float return fn(x, **kw) 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 """ FMT = { 'a':self._fmt_auto, 'i':self._fmt_int, 'f':self._fmt_float, 'e':self._fmt_exp, 't':self._fmt_text, } n = self._precision dtype = self._dtype[i] try: if callable(dtype): return dtype(x) else: return FMT[dtype](x, n=n) except FallbackToText: return self._fmt_text(x) 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 = s.join([horiz * n for n in self._width]) # 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, list(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, list(range(len(row)))): try: maxi[i] = max(maxi[i], self._len_cell(cell)) except (TypeError, IndexError): maxi.append(self._len_cell(cell)) ncols = len(maxi) content_width = sum(maxi) deco_width = 3*(ncols-1) + [0,4][self._has_border()] if self._max_width and (content_width + deco_width) > self._max_width: """ content too wide to fit the expected max_width let's recompute maximum cell width for each cell """ if self._max_width < (ncols + deco_width): raise ValueError('max_width too low to render data') available_width = self._max_width - deco_width newmaxi = [0] * ncols i = 0 while available_width > 0: if newmaxi[i] < maxi[i]: newmaxi[i] += 1 available_width -= 1 i = (i + 1) % ncols maxi = newmaxi self._width = maxi def _check_align(self): """Check if alignment has been specified, set default one if not """ if not hasattr(self, "_header_align"): self._header_align = ["c"] * self._row_size 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 = self._header_align[length - 1] if align == "r": out += fill * space + cell_line elif align == "c": out += (int(fill/2) * space + cell_line \ + int(fill/2 + fill%2) * space) else: out += cell_line + fill * space if length < len(line): out += " %s " % [space, self._char_vert][self._has_vlines()] out += "%s\n" % ['', space + 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'): if c.strip() == "": array.append("") else: array.extend(textwrapper(c, width)) line_wrapped.append(array) max_cell_lines = reduce(max, list(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] = [""] * int(missing / 2) cell.extend([""] * int(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"], ["Mme\nLouise\nBourgeau", 28, "Lou\n \nLoue"]]) print(table.draw()) print() 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()) texttable-1.6.4/texttable.pyi000066400000000000000000000020731407334552300162450ustar00rootroot00000000000000from typing import * class Texttable: BORDER: int HEADER: int HLINES: int VLINES: int def __init__(self, max_width: int = ...) -> None: ... def reset(self) -> 'Texttable': ... def set_max_width(self, max_width: int) -> 'Texttable': ... def set_chars(self, array: List[str]) -> 'Texttable': ... def set_deco(self, deco: int) -> 'Texttable': ... def set_header_align(self, array: List[str]) -> 'Texttable': ... def set_cols_align(self, array: List[str]) -> 'Texttable': ... def set_cols_valign(self, array: List[str]) -> 'Texttable': ... def set_cols_dtype(self, array: List[Union[str, Callable[[Any], str]]]) -> 'Texttable': ... def set_cols_width(self, array: List[int]) -> 'Texttable': ... def set_precision(self, width: int) -> 'Texttable': ... def header(self, array: List[str]) -> 'Texttable': ... def add_row(self, array: List[Union[Union[int, str], float]]) -> 'Texttable': ... def add_rows(self, rows: List[object], header: bool = ...) -> 'Texttable': ... def draw(self) -> str: ... texttable-1.6.4/tox.ini000066400000000000000000000002531407334552300150370ustar00rootroot00000000000000[tox] envlist = py27,py35,py36,py37,py38,py39 [testenv] deps = pytest pytest-cov cjkwrap commands = pytest --cov-report=term-missing --cov=texttable tests.py