diff options
| author | Sandro Tosi <morph@debian.org> | 2016-12-16 19:37:17 -0500 |
|---|---|---|
| committer | git-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com> | 2016-12-17 04:20:57 +0000 |
| commit | f68be773822d9dde393354c2c43a5bdf5ea2caef (patch) | |
| tree | a70af822ea77a0993d352630dd992e670595f4c3 | |
| parent | 7054a003ef9a0c8128cf2fe2c9d1db7ff45f73c5 (diff) | |
0.7.7-1 (patches unapplied)import/0.7.7-1ubuntu/zesty-proposedubuntu/zesty-develubuntu/zestyubuntu/bionic-develubuntu/bionicubuntu/artful-develubuntu/artfuldebian/stretch
Imported using git-ubuntu import.
Notes
Notes:
[ Sandro Tosi ]
* New upstream release
* debian/control
- adjust Vcs-Browser to DPMT standards
* debian/control
- update upstream copyright years
- add copyright for my contributions
- add wcwidth to b-d
- bump Standards-Version to 3.9.8 (no changes needed)
* debian/python-tabulate.examples
- dont install benchmarl.py in py2 package, remove from the tarball
* debian/rules
- dont run tests (for now)
* compat level 10
[ Ondřej Nový ]
* Fixed VCS URL (https)
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | HOWTOPUBLISH | 6 | ||||
| -rw-r--r-- | LICENSE | 40 | ||||
| -rw-r--r-- | MANIFEST.in | 6 | ||||
| -rw-r--r-- | PKG-INFO | 590 | ||||
| -rw-r--r--[l---------] | README | 0 | ||||
| -rw-r--r-- | README.rst | 1043 | ||||
| -rw-r--r-- | benchmark.py | 97 | ||||
| -rw-r--r-- | debian/.git-dpm | 11 | ||||
| -rw-r--r-- | debian/changelog | 22 | ||||
| -rw-r--r-- | debian/compat | 2 | ||||
| -rw-r--r-- | debian/control | 12 | ||||
| -rw-r--r-- | debian/copyright | 5 | ||||
| -rw-r--r-- | debian/python-tabulate.examples | 2 | ||||
| -rwxr-xr-x | debian/rules | 3 | ||||
| -rw-r--r-- | setup.cfg | 5 | ||||
| -rw-r--r-- | setup.py | 118 | ||||
| -rw-r--r-- | tabulate.egg-info/.PKG-INFO.swp | bin | 0 -> 16384 bytes | |||
| -rw-r--r-- | tabulate.egg-info/PKG-INFO | 590 | ||||
| -rw-r--r-- | tabulate.egg-info/SOURCES.txt | 18 | ||||
| -rw-r--r-- | tabulate.egg-info/dependency_links.txt | 1 | ||||
| -rw-r--r-- | tabulate.egg-info/entry_points.txt | 3 | ||||
| -rw-r--r-- | tabulate.egg-info/requires.txt | 3 | ||||
| -rw-r--r-- | tabulate.egg-info/top_level.txt | 1 | ||||
| -rw-r--r-- | tabulate.py | 2420 | ||||
| -rw-r--r-- | test/common.py | 31 | ||||
| -rw-r--r-- | test/test_api.py | 112 | ||||
| -rw-r--r-- | test/test_cli.py | 380 | ||||
| -rw-r--r-- | test/test_input.py | 853 | ||||
| -rw-r--r-- | test/test_output.py | 943 | ||||
| -rw-r--r-- | test/test_regression.py | 566 | ||||
| -rw-r--r-- | tox.ini | 79 |
32 files changed, 4859 insertions, 3108 deletions
diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8e9be6f..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build -dist -.tox -*~ -*.pyc diff --git a/HOWTOPUBLISH b/HOWTOPUBLISH deleted file mode 100644 index 7daf99b..0000000 --- a/HOWTOPUBLISH +++ /dev/null @@ -1,6 +0,0 @@ -# bump version number in setup.py and tabulate.py -python2 benchmark.py # then update README -tox -e py26,py27-extra,py32,py33-extra,py34 -python2 setup.py register -n -python2 setup.py register sdist upload -# tag version release @@ -1,20 +1,20 @@ -Copyright (c) 2011-2014 Sergey Astanin - -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. +Copyright (c) 2011-2016 Sergey Astanin
+
+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.
diff --git a/MANIFEST.in b/MANIFEST.in index 09e7fcf..63a18f5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -include LICENSE -include README -include README.rst +include LICENSE
+include README
+include README.rst
diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..dda9777 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,590 @@ +Metadata-Version: 1.1
+Name: tabulate
+Version: 0.7.7
+Summary: Pretty-print tabular data
+Home-page: https://bitbucket.org/astanin/python-tabulate
+Author: Sergey Astanin
+Author-email: s.astanin@gmail.com
+License: Copyright (c) 2011-2016 Sergey Astanin
+
+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.
+
+Description: ===============
+ python-tabulate
+ ===============
+
+ Pretty-print tabular data in Python, a library and a command-line
+ utility.
+
+ The main use cases of the library are:
+
+ * printing small tables without hassle: just one function call,
+ formatting is guided by the data itself
+
+ * authoring tabular data for lightweight plain-text markup: multiple
+ output formats suitable for further editing or transformation
+
+ * readable presentation of mixed textual and numeric data: smart
+ column alignment, configurable number formatting, alignment by a
+ decimal point
+
+
+ Installation
+ ------------
+
+ To install the Python library and the command line utility, run::
+
+ pip install tabulate
+
+ The command line utility will be installed as ``tabulate`` to ``bin`` on Linux
+ (e.g. ``/usr/bin``); or as ``tabulate.exe`` to ``Scripts`` in your Python
+ installation on Windows (e.g. ``C:\Python27\Scripts\tabulate.exe``).
+
+ You may consider installing the library only for the current user::
+
+ pip install tabulate --user
+
+ In this case the command line utility will be installed to ``~/.local/bin/tabulate``
+ on Linux and to ``%APPDATA%\Python\Scripts\tabulate.exe`` on Windows.
+
+ To install just the library on Unix-like operating systems::
+
+ TABULATE_INSTALL=lib-only pip install tabulate
+
+ On Windows::
+
+ set TABULATE_INSTALL=lib-only
+ pip install tabulate
+
+
+ Library usage
+ -------------
+
+ The module provides just one function, ``tabulate``, which takes a
+ list of lists or another tabular data type as the first argument,
+ and outputs a nicely formatted plain-text table::
+
+ >>> from tabulate import tabulate
+
+ >>> table = [["Sun",696000,1989100000],["Earth",6371,5973.6],
+ ... ["Moon",1737,73.5],["Mars",3390,641.85]]
+ >>> print tabulate(table)
+ ----- ------ -------------
+ Sun 696000 1.9891e+09
+ Earth 6371 5973.6
+ Moon 1737 73.5
+ Mars 3390 641.85
+ ----- ------ -------------
+
+ The following tabular data types are supported:
+
+ * list of lists or another iterable of iterables
+ * list or another iterable of dicts (keys as columns)
+ * dict of iterables (keys as columns)
+ * two-dimensional NumPy array
+ * NumPy record arrays (names as columns)
+ * pandas.DataFrame
+
+ Examples in this file use Python2. Tabulate supports Python3 too.
+
+
+ Headers
+ ~~~~~~~
+
+ The second optional argument named ``headers`` defines a list of
+ column headers to be used::
+
+ >>> print tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"])
+ Planet R (km) mass (x 10^29 kg)
+ -------- -------- -------------------
+ Sun 696000 1.9891e+09
+ Earth 6371 5973.6
+ Moon 1737 73.5
+ Mars 3390 641.85
+
+ If ``headers="firstrow"``, then the first row of data is used::
+
+ >>> print tabulate([["Name","Age"],["Alice",24],["Bob",19]],
+ ... headers="firstrow")
+ Name Age
+ ------ -----
+ Alice 24
+ Bob 19
+
+
+ If ``headers="keys"``, then the keys of a dictionary/dataframe, or
+ column indices are used. It also works for NumPy record arrays and
+ lists of dictionaries or named tuples::
+
+ >>> print tabulate({"Name": ["Alice", "Bob"],
+ ... "Age": [24, 19]}, headers="keys")
+ Age Name
+ ----- ------
+ 24 Alice
+ 19 Bob
+
+
+ Row Indices
+ ~~~~~~~~~~~
+
+ By default, only pandas.DataFrame tables have an additional column
+ called row index. To add a similar column to any other type of table,
+ pass ``showindex="always"`` or ``showindex=True`` argument to
+ ``tabulate()``. To suppress row indices for all types of data, pass
+ ``showindex="never"`` or ``showindex=False``. To add a custom row
+ index column, pass ``showindex=rowIDs``, where ``rowIDs`` is some
+ iterable::
+
+ >>> print(tabulate([["F",24],["M",19]], showindex="always"))
+ - - --
+ 0 F 24
+ 1 M 19
+ - - --
+
+
+ Table format
+ ~~~~~~~~~~~~
+
+ There is more than one way to format a table in plain text.
+ The third optional argument named ``tablefmt`` defines
+ how the table is formatted.
+
+ Supported table formats are:
+
+ - "plain"
+ - "simple"
+ - "grid"
+ - "fancy_grid"
+ - "pipe"
+ - "orgtbl"
+ - "jira"
+ - "psql"
+ - "rst"
+ - "mediawiki"
+ - "moinmoin"
+ - "html"
+ - "latex"
+ - "latex_booktabs"
+ - "textile"
+
+ ``plain`` tables do not use any pseudo-graphics to draw lines::
+
+ >>> table = [["spam",42],["eggs",451],["bacon",0]]
+ >>> headers = ["item", "qty"]
+ >>> print tabulate(table, headers, tablefmt="plain")
+ item qty
+ spam 42
+ eggs 451
+ bacon 0
+
+ ``simple`` is the default format (the default may change in future
+ versions). It corresponds to ``simple_tables`` in `Pandoc Markdown
+ extensions`::
+
+ >>> print tabulate(table, headers, tablefmt="simple")
+ item qty
+ ------ -----
+ spam 42
+ eggs 451
+ bacon 0
+
+ ``grid`` is like tables formatted by Emacs' `table.el`
+ package. It corresponds to ``grid_tables`` in Pandoc Markdown
+ extensions::
+
+ >>> print tabulate(table, headers, tablefmt="grid")
+ +--------+-------+
+ | item | qty |
+ +========+=======+
+ | spam | 42 |
+ +--------+-------+
+ | eggs | 451 |
+ +--------+-------+
+ | bacon | 0 |
+ +--------+-------+
+
+ ``fancy_grid`` draws a grid using box-drawing characters::
+
+ >>> print tabulate(table, headers, tablefmt="fancy_grid")
+ ╒════════╤═══════╕
+ │ item │ qty │
+ ╞════════╪═══════╡
+ │ spam │ 42 │
+ ├────────┼───────┤
+ │ eggs │ 451 │
+ ├────────┼───────┤
+ │ bacon │ 0 │
+ ╘════════╧═══════╛
+
+ ``psql`` is like tables formatted by Postgres' psql cli::
+
+ >>> print tabulate.tabulate()
+ +--------+-------+
+ | item | qty |
+ |--------+-------|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+ +--------+-------+
+
+ ``pipe`` follows the conventions of `PHP Markdown Extra` extension. It
+ corresponds to ``pipe_tables`` in Pandoc. This format uses colons to
+ indicate column alignment::
+
+ >>> print tabulate(table, headers, tablefmt="pipe")
+ | item | qty |
+ |:-------|------:|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+ ``orgtbl`` follows the conventions of Emacs `org-mode`, and is editable
+ also in the minor `orgtbl-mode`. Hence its name::
+
+ >>> print tabulate(table, headers, tablefmt="orgtbl")
+ | item | qty |
+ |--------+-------|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+ ``jira`` follows the conventions of Atlassian Jira markup language::
+
+ >>> print tabulate(table, headers, tablefmt="jira")
+ || item || qty ||
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+ ``rst`` formats data like a simple table of the `reStructuredText` format::
+
+ >>> print tabulate(table, headers, tablefmt="rst")
+ ====== =====
+ item qty
+ ====== =====
+ spam 42
+ eggs 451
+ bacon 0
+ ====== =====
+
+ ``mediawiki`` format produces a table markup used in `Wikipedia` and on
+ other MediaWiki-based sites::
+
+ >>> print tabulate(table, headers, tablefmt="mediawiki")
+ {| class="wikitable" style="text-align: left;"
+ |+ <!-- caption -->
+ |-
+ ! item !! align="right"| qty
+ |-
+ | spam || align="right"| 42
+ |-
+ | eggs || align="right"| 451
+ |-
+ | bacon || align="right"| 0
+ |}
+
+ ``moinmoin`` format produces a table markup used in `MoinMoin`
+ wikis::
+
+ >>> print tabulate(d,headers,tablefmt="moinmoin")
+ || ''' item ''' || ''' quantity ''' ||
+ || spam || 41.999 ||
+ || eggs || 451 ||
+ || bacon || ||
+
+ ``textile`` format produces a table markup used in `Textile` format::
+
+ >>> print tabulate(table, headers, tablefmt='textile')
+ |_. item |_. qty |
+ |<. spam |>. 42 |
+ |<. eggs |>. 451 |
+ |<. bacon |>. 0 |
+
+ ``html`` produces standard HTML markup::
+
+ >>> print tabulate(table, headers, tablefmt="html")
+ <table>
+ <tbody>
+ <tr><th>item </th><th style="text-align: right;"> qty</th></tr>
+ <tr><td>spam </td><td style="text-align: right;"> 42</td></tr>
+ <tr><td>eggs </td><td style="text-align: right;"> 451</td></tr>
+ <tr><td>bacon </td><td style="text-align: right;"> 0</td></tr>
+ </tbody>
+ </table>
+
+ ``latex`` format creates a ``tabular`` environment for LaTeX markup::
+
+ >>> print tabulate(table, headers, tablefmt="latex")
+ \begin{tabular}{lr}
+ \hline
+ item & qty \\
+ \hline
+ spam & 42 \\
+ eggs & 451 \\
+ bacon & 0 \\
+ \hline
+ \end{tabular}
+
+ ``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup
+ using spacing and style from the ``booktabs`` package.
+
+
+ .. _Pandoc Markdown extensions: http://johnmacfarlane.net/pandoc/README.html#tables
+ .. _PHP Markdown Extra: http://michelf.ca/projects/php-markdown/extra/#table
+ .. _table.el: http://table.sourceforge.net/
+ .. _org-mode: http://orgmode.org/manual/Tables.html
+ .. _reStructuredText: http://docutils.sourceforge.net/docs/user/rst/quickref.html#tables
+ .. _Textile: http://redcloth.org/hobix.com/textile/
+ .. _Wikipedia: http://www.mediawiki.org/wiki/Help:Tables
+
+
+ Column alignment
+ ~~~~~~~~~~~~~~~~
+
+ ``tabulate`` is smart about column alignment. It detects columns which
+ contain only numbers, and aligns them by a decimal point (or flushes
+ them to the right if they appear to be integers). Text columns are
+ flushed to the left.
+
+ You can override the default alignment with ``numalign`` and
+ ``stralign`` named arguments. Possible column alignments are:
+ ``right``, ``center``, ``left``, ``decimal`` (only for numbers), and
+ ``None`` (to disable alignment).
+
+ Aligning by a decimal point works best when you need to compare
+ numbers at a glance::
+
+ >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]])
+ ----------
+ 1.2345
+ 123.45
+ 12.345
+ 12345
+ 1234.5
+ ----------
+
+ Compare this with a more common right alignment::
+
+ >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]], numalign="right")
+ ------
+ 1.2345
+ 123.45
+ 12.345
+ 12345
+ 1234.5
+ ------
+
+ For ``tabulate``, anything which can be parsed as a number is a
+ number. Even numbers represented as strings are aligned properly. This
+ feature comes in handy when reading a mixed table of text and numbers
+ from a file:
+
+ ::
+
+ >>> import csv ; from StringIO import StringIO
+ >>> table = list(csv.reader(StringIO("spam, 42\neggs, 451\n")))
+ >>> table
+ [['spam', ' 42'], ['eggs', ' 451']]
+ >>> print tabulate(table)
+ ---- ----
+ spam 42
+ eggs 451
+ ---- ----
+
+
+
+ Number formatting
+ ~~~~~~~~~~~~~~~~~
+
+ ``tabulate`` allows to define custom number formatting applied to all
+ columns of decimal numbers. Use ``floatfmt`` named argument::
+
+
+ >>> print tabulate([["pi",3.141593],["e",2.718282]], floatfmt=".4f")
+ -- ------
+ pi 3.1416
+ e 2.7183
+ -- ------
+
+
+ Wide (fullwidth CJK) symbols
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ To properly align tables which contain wide characters (typically fullwidth
+ glyphs from Chinese, Japanese or Korean languages), the user should install
+ ``wcwidth`` library. To install it together with ``tabulate``::
+
+ pip install tabulate[widechars]
+
+ Wide character support is enabled automatically if ``wcwidth`` library is
+ already installed. To disable wide characters support without uninstalling
+ ``wcwidth``, set the global module-level flag ``WIDE_CHARS_MODE``::
+
+ import tabulate
+ tabulate.WIDE_CHARS_MODE = False
+
+
+ Usage of the command line utility
+ ---------------------------------
+
+ ::
+
+ Usage: tabulate [options] [FILE ...]
+
+ FILE a filename of the file with tabular data;
+ if "-" or missing, read data from stdin.
+
+ Options:
+
+ -h, --help show this message
+ -1, --header use the first row of data as a table header
+ -o FILE, --output FILE print table to FILE (default: stdout)
+ -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace)
+ -F FPFMT, --float FPFMT floating point number format (default: g)
+ -f FMT, --format FMT set output table format; supported formats:
+ plain, simple, grid, fancy_grid, pipe, orgtbl,
+ rst, mediawiki, html, latex, latex_booktabs, tsv
+ (default: simple)
+
+
+ Performance considerations
+ --------------------------
+
+ Such features as decimal point alignment and trying to parse everything
+ as a number imply that ``tabulate``:
+
+ * has to "guess" how to print a particular tabular data type
+ * needs to keep the entire table in-memory
+ * has to "transpose" the table twice
+ * does much more work than it may appear
+
+ It may not be suitable for serializing really big tables (but who's
+ going to do that, anyway?) or printing tables in performance sensitive
+ applications. ``tabulate`` is about two orders of magnitude slower
+ than simply joining lists of values with a tab, coma or other
+ separator.
+
+ In the same time ``tabulate`` is comparable to other table
+ pretty-printers. Given a 10x10 table (a list of lists) of mixed text
+ and numeric data, ``tabulate`` appears to be slower than
+ ``asciitable``, and faster than ``PrettyTable`` and ``texttable``
+
+ ::
+
+ ================================= ========== ===========
+ Table formatter time, μs rel. time
+ ================================= ========== ===========
+ csv to StringIO 25.3 1.0
+ join with tabs and newlines 33.6 1.3
+ asciitable (0.8.0) 590.0 23.4
+ tabulate (0.7.7) 1403.5 55.6
+ tabulate (0.7.7, WIDE_CHARS_MODE) 2156.6 85.4
+ PrettyTable (0.7.2) 3377.0 133.7
+ texttable (0.8.6) 3986.3 157.8
+ ================================= ========== ===========
+
+
+ Version history
+ ---------------
+
+ - 0.8: FUTURE RELEASE
+ - 0.7.6: Bug fixes. New table formats (``psql``, ``jira``, ``moinmoin``, ``textile``).
+ Wide character support. Printing from database cursors.
+ Option to print row indices. Boolean columns. Ragged rows.
+ Option to disable number parsing.
+ - 0.7.5: Bug fixes. ``--float`` format option for the command line utility.
+ - 0.7.4: Bug fixes. ``fancy_grid`` and ``html`` formats. Command line utility.
+ - 0.7.3: Bug fixes. Python 3.4 support. Iterables of dicts. ``latex_booktabs`` format.
+ - 0.7.2: Python 3.2 support.
+ - 0.7.1: Bug fixes. ``tsv`` format. Column alignment can be disabled.
+ - 0.7: ``latex`` tables. Printing lists of named tuples and NumPy
+ record arrays. Fix printing date and time values. Python <= 2.6.4 is supported.
+ - 0.6: ``mediawiki`` tables, bug fixes.
+ - 0.5.1: Fix README.rst formatting. Optimize (performance similar to 0.4.4).
+ - 0.5: ANSI color sequences. Printing dicts of iterables and Pandas' dataframes.
+ - 0.4.4: Python 2.6 support.
+ - 0.4.3: Bug fix, None as a missing value.
+ - 0.4.2: Fix manifest file.
+ - 0.4.1: Update license and documentation.
+ - 0.4: Unicode support, Python3 support, ``rst`` tables.
+ - 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``,
+ ``grid``, ``pipe``, and ``orgtbl``.
+
+
+ How to contribute
+ -----------------
+
+ Contributions should include tests and an explanation for the changes they
+ propose. Documentation (examples, docstrings, README.rst) should be updated
+ accordingly.
+
+ This project uses `nose` testing framework and `tox` to automate testing in
+ different environments. Add tests to one of the files in the ``test/`` folder.
+
+ To run tests on all supported Python versions, make sure all Python
+ interpreters, ``nose`` and ``tox`` are installed, then run ``tox`` in
+ the root of the project source tree.
+
+ On Linux ``tox`` expects to find executables like ``python2.6``,
+ ``python2.7``, ``python3.4`` etc. On Windows it looks for
+ ``C:\Python26\python.exe``, ``C:\Python27\python.exe`` and
+ ``C:\Python34\python.exe`` respectively.
+
+ To test only some Python environements, use ``-e`` option. For
+ example, to test only against Python 2.7 and Python 3.4, run::
+
+ tox -e py27,py34
+
+ in the root of the project source tree.
+
+ To enable NumPy and Pandas tests, run::
+
+ tox -e py27-extra,py34-extra
+
+ (this may take a long time the first time, because NumPy and Pandas
+ will have to be installed in the new virtual environments)
+
+ See ``tox.ini`` file to learn how to use ``nosetests`` directly to
+ test individual Python versions.
+
+ .. _nose: https://nose.readthedocs.org/
+ .. _tox: http://tox.testrun.org/
+
+
+ Contributors
+ ------------
+
+ Sergey Astanin, Pau Tallada Crespí, Erwin Marsi, Mik Kocikowski, Bill Ryder,
+ Zach Dwiel, Frederik Rietdijk, Philipp Bogensberger, Greg (anonymous),
+ Stefan Tatschner, Emiel van Miltenburg, Brandon Bennett, Amjith Ramanujam,
+ Jan Schulz, Simon Percivall, Javier Santacruz López-Cepero, Sam Denton,
+ Alexey Ziyangirov, acaird, Cesar Sanchez, naught101, John Vandenberg,
+ Zack Dever.
+
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development :: Libraries
@@ -1,484 +1,559 @@ -=============== -python-tabulate -=============== - -Pretty-print tabular data in Python, a library and a command-line -utility. - -The main use cases of the library are: - -* printing small tables without hassle: just one function call, - formatting is guided by the data itself - -* authoring tabular data for lightweight plain-text markup: multiple - output formats suitable for further editing or transformation - -* readable presentation of mixed textual and numeric data: smart - column alignment, configurable number formatting, alignment by a - decimal point - - -Installation ------------- - -To install the Python library and the command line utility, run:: - - pip install tabulate - -The command line utility will be installed as ``tabulate`` to ``bin`` on Linux -(e.g. ``/usr/bin``); or as ``tabulate.exe`` to ``Scripts`` in your Python -installation on Windows (e.g. ``C:\Python27\Scripts\tabulate.exe``). - -You may consider installing the library only for the current user:: - - pip install tabulate --user - -In this case the command line utility will be installed to ``~/.local/bin/tabulate`` -on Linux and to ``%APPDATA%\Python\Scripts\tabulate.exe`` on Windows. - -To install just the library on Unix-like operating systems:: - - TABULATE_INSTALL=lib-only pip install tabulate - -On Windows:: - - set TABULATE_INSTALL=lib-only - pip install tabulate - - -Build status ------------- - -.. image:: https://drone.io/bitbucket.org/astanin/python-tabulate/status.png - :alt: Build status - :target: https://drone.io/bitbucket.org/astanin/python-tabulate/latest - - -Library usage -------------- - -The module provides just one function, ``tabulate``, which takes a -list of lists or another tabular data type as the first argument, -and outputs a nicely formatted plain-text table:: - - >>> from tabulate import tabulate - - >>> table = [["Sun",696000,1989100000],["Earth",6371,5973.6], - ... ["Moon",1737,73.5],["Mars",3390,641.85]] - >>> print tabulate(table) - ----- ------ ------------- - Sun 696000 1.9891e+09 - Earth 6371 5973.6 - Moon 1737 73.5 - Mars 3390 641.85 - ----- ------ ------------- - -The following tabular data types are supported: - -* list of lists or another iterable of iterables -* list or another iterable of dicts (keys as columns) -* dict of iterables (keys as columns) -* two-dimensional NumPy array -* NumPy record arrays (names as columns) -* pandas.DataFrame - -Examples in this file use Python2. Tabulate supports Python3 too. - - -Headers -~~~~~~~ - -The second optional argument named ``headers`` defines a list of -column headers to be used:: - - >>> print tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"]) - Planet R (km) mass (x 10^29 kg) - -------- -------- ------------------- - Sun 696000 1.9891e+09 - Earth 6371 5973.6 - Moon 1737 73.5 - Mars 3390 641.85 - -If ``headers="firstrow"``, then the first row of data is used:: - - >>> print tabulate([["Name","Age"],["Alice",24],["Bob",19]], - ... headers="firstrow") - Name Age - ------ ----- - Alice 24 - Bob 19 - - -If ``headers="keys"``, then the keys of a dictionary/dataframe, or -column indices are used. It also works for NumPy record arrays and -lists of dictionaries or named tuples:: - - >>> print tabulate({"Name": ["Alice", "Bob"], - ... "Age": [24, 19]}, headers="keys") - Age Name - ----- ------ - 24 Alice - 19 Bob - - -Table format -~~~~~~~~~~~~ - -There is more than one way to format a table in plain text. -The third optional argument named ``tablefmt`` defines -how the table is formatted. - -Supported table formats are: - -- "plain" -- "simple" -- "grid" -- "fancy_grid" -- "pipe" -- "orgtbl" -- "rst" -- "mediawiki" -- "html" -- "latex" -- "latex_booktabs" - -``plain`` tables do not use any pseudo-graphics to draw lines:: - - >>> table = [["spam",42],["eggs",451],["bacon",0]] - >>> headers = ["item", "qty"] - >>> print tabulate(table, headers, tablefmt="plain") - item qty - spam 42 - eggs 451 - bacon 0 - -``simple`` is the default format (the default may change in future -versions). It corresponds to ``simple_tables`` in `Pandoc Markdown -extensions`_:: - - >>> print tabulate(table, headers, tablefmt="simple") - item qty - ------ ----- - spam 42 - eggs 451 - bacon 0 - -``grid`` is like tables formatted by Emacs' `table.el`_ -package. It corresponds to ``grid_tables`` in Pandoc Markdown -extensions:: - - >>> print tabulate(table, headers, tablefmt="grid") - +--------+-------+ - | item | qty | - +========+=======+ - | spam | 42 | - +--------+-------+ - | eggs | 451 | - +--------+-------+ - | bacon | 0 | - +--------+-------+ - -``fancy_grid`` draws a grid using box-drawing characters:: - - >>> print tabulate(table, headers, tablefmt="fancy_grid") - ╒════════╤═══════╕ - │ item │ qty │ - ╞════════╪═══════╡ - │ spam │ 42 │ - ├────────┼───────┤ - │ eggs │ 451 │ - ├────────┼───────┤ - │ bacon │ 0 │ - ╘════════╧═══════╛ - -``psql`` is like tables formatted by Postgres' psql cli:: - - >>> print tabulate.tabulate() - +--------+-------+ - | item | qty | - |--------+-------| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - +--------+-------+ - -``pipe`` follows the conventions of `PHP Markdown Extra`_ extension. It -corresponds to ``pipe_tables`` in Pandoc. This format uses colons to -indicate column alignment:: - - >>> print tabulate(table, headers, tablefmt="pipe") - | item | qty | - |:-------|------:| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - -``orgtbl`` follows the conventions of Emacs `org-mode`_, and is editable -also in the minor `orgtbl-mode`. Hence its name:: - - >>> print tabulate(table, headers, tablefmt="orgtbl") - | item | qty | - |--------+-------| - | spam | 42 | - | eggs | 451 | - | bacon | 0 | - -``rst`` formats data like a simple table of the `reStructuredText`_ format:: - - >>> print tabulate(table, headers, tablefmt="rst") - ====== ===== - item qty - ====== ===== - spam 42 - eggs 451 - bacon 0 - ====== ===== - -``mediawiki`` format produces a table markup used in `Wikipedia`_ and on -other MediaWiki-based sites:: - - >>> print tabulate(table, headers, tablefmt="mediawiki") - {| class="wikitable" style="text-align: left;" - |+ <!-- caption --> - |- - ! item !! align="right"| qty - |- - | spam || align="right"| 42 - |- - | eggs || align="right"| 451 - |- - | bacon || align="right"| 0 - |} - -``html`` produces standard HTML markup:: - - >>> print tabulate(table, headers, tablefmt="html") - <table> - <tr><th>item </th><th style="text-align: right;"> qty</th></tr> - <tr><td>spam </td><td style="text-align: right;"> 42</td></tr> - <tr><td>eggs </td><td style="text-align: right;"> 451</td></tr> - <tr><td>bacon </td><td style="text-align: right;"> 0</td></tr> - </table> - -``latex`` format creates a ``tabular`` environment for LaTeX markup:: - - >>> print tabulate(table, headers, tablefmt="latex") - \begin{tabular}{lr} - \hline - item & qty \\ - \hline - spam & 42 \\ - eggs & 451 \\ - bacon & 0 \\ - \hline - \end{tabular} - -``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup -using spacing and style from the ``booktabs`` package. - - -.. _Pandoc Markdown extensions: http://johnmacfarlane.net/pandoc/README.html#tables -.. _PHP Markdown Extra: http://michelf.ca/projects/php-markdown/extra/#table -.. _table.el: http://table.sourceforge.net/ -.. _org-mode: http://orgmode.org/manual/Tables.html -.. _reStructuredText: http://docutils.sourceforge.net/docs/user/rst/quickref.html#tables -.. _Wikipedia: http://www.mediawiki.org/wiki/Help:Tables - - -Column alignment -~~~~~~~~~~~~~~~~ - -``tabulate`` is smart about column alignment. It detects columns which -contain only numbers, and aligns them by a decimal point (or flushes -them to the right if they appear to be integers). Text columns are -flushed to the left. - -You can override the default alignment with ``numalign`` and -``stralign`` named arguments. Possible column alignments are: -``right``, ``center``, ``left``, ``decimal`` (only for numbers), and -``None`` (to disable alignment). - -Aligning by a decimal point works best when you need to compare -numbers at a glance:: - - >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]]) - ---------- - 1.2345 - 123.45 - 12.345 - 12345 - 1234.5 - ---------- - -Compare this with a more common right alignment:: - - >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]], numalign="right") - ------ - 1.2345 - 123.45 - 12.345 - 12345 - 1234.5 - ------ - -For ``tabulate``, anything which can be parsed as a number is a -number. Even numbers represented as strings are aligned properly. This -feature comes in handy when reading a mixed table of text and numbers -from a file: - -:: - - >>> import csv ; from StringIO import StringIO - >>> table = list(csv.reader(StringIO("spam, 42\neggs, 451\n"))) - >>> table - [['spam', ' 42'], ['eggs', ' 451']] - >>> print tabulate(table) - ---- ---- - spam 42 - eggs 451 - ---- ---- - - - -Number formatting -~~~~~~~~~~~~~~~~~ - -``tabulate`` allows to define custom number formatting applied to all -columns of decimal numbers. Use ``floatfmt`` named argument:: - - - >>> print tabulate([["pi",3.141593],["e",2.718282]], floatfmt=".4f") - -- ------ - pi 3.1416 - e 2.7183 - -- ------ - - -Usage of the command line utility ---------------------------------- - -:: - - Usage: tabulate [options] [FILE ...] - - FILE a filename of the file with tabular data; - if "-" or missing, read data from stdin. - - Options: - - -h, --help show this message - -1, --header use the first row of data as a table header - -o FILE, --output FILE print table to FILE (default: stdout) - -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) - -F FPFMT, --float FPFMT floating point number format (default: g) - -f FMT, --format FMT set output table format; supported formats: - plain, simple, grid, fancy_grid, pipe, orgtbl, - rst, mediawiki, html, latex, latex_booktabs, tsv - (default: simple) - - -Performance considerations --------------------------- - -Such features as decimal point alignment and trying to parse everything -as a number imply that ``tabulate``: - -* has to "guess" how to print a particular tabular data type -* needs to keep the entire table in-memory -* has to "transpose" the table twice -* does much more work than it may appear - -It may not be suitable for serializing really big tables (but who's -going to do that, anyway?) or printing tables in performance sensitive -applications. ``tabulate`` is about two orders of magnitude slower -than simply joining lists of values with a tab, coma or other -separator. - -In the same time ``tabulate`` is comparable to other table -pretty-printers. Given a 10x10 table (a list of lists) of mixed text -and numeric data, ``tabulate`` appears to be slower than -``asciitable``, and faster than ``PrettyTable`` and ``texttable`` - -:: - - =========================== ========== =========== - Table formatter time, μs rel. time - =========================== ========== =========== - join with tabs and newlines 36.4 1.0 - csv to StringIO 48.6 1.3 - tabletext (0.1) 876.9 24.1 - asciitable (0.8.0) 1198.3 32.9 - tabulate (0.7.5) 2211.9 60.8 - PrettyTable (0.7.2) 5727.3 157.5 - texttable (0.8.1) 6080.5 167.2 - =========================== ========== =========== - - -Version history ---------------- - -- 0.7.5: Bug fixes. ``--float`` format option for the command line utility. -- 0.7.4: Bug fixes. ``fancy_grid`` and ``html`` formats. Command line utility. -- 0.7.3: Bug fixes. Python 3.4 support. Iterables of dicts. ``latex_booktabs`` format. -- 0.7.2: Python 3.2 support. -- 0.7.1: Bug fixes. ``tsv`` format. Column alignment can be disabled. -- 0.7: ``latex`` tables. Printing lists of named tuples and NumPy - record arrays. Fix printing date and time values. Python <= 2.6.4 is supported. -- 0.6: ``mediawiki`` tables, bug fixes. -- 0.5.1: Fix README.rst formatting. Optimize (performance similar to 0.4.4). -- 0.5: ANSI color sequences. Printing dicts of iterables and Pandas' dataframes. -- 0.4.4: Python 2.6 support. -- 0.4.3: Bug fix, None as a missing value. -- 0.4.2: Fix manifest file. -- 0.4.1: Update license and documentation. -- 0.4: Unicode support, Python3 support, ``rst`` tables. -- 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``, - ``grid``, ``pipe``, and ``orgtbl``. - - -How to contribute ------------------ - -Contributions should include tests and an explanation for the changes they -propose. Documentation (examples, docstrings, README.rst) should be updated -accordingly. - -This project uses `nose`_ testing framework and `tox`_ to automate testing in -different environments. Add tests to one of the files in the ``test/`` folder. - -To run tests on all supported Python versions, make sure all Python -interpreters, ``nose`` and ``tox`` are installed, then run ``tox`` in -the root of the project source tree. - -On Linux ``tox`` expects to find executables like ``python2.6``, -``python2.7``, ``python3.4`` etc. On Windows it looks for -``C:\Python26\python.exe``, ``C:\Python27\python.exe`` and -``C:\Python34\python.exe`` respectively. - -To test only some Python environements, use ``-e`` option. For -example, to test only against Python 2.7 and Python 3.4, run:: - - tox -e py27,py34 - -in the root of the project source tree. - -To enable NumPy and Pandas tests, run:: - - tox -e py27-extra,py34-extra - -(this may take a long time the first time, because NumPy and Pandas -will have to be installed in the new virtual environments) - -See ``tox.ini`` file to learn how to use ``nosetests`` directly to -test individual Python versions. - -.. _nose: https://nose.readthedocs.org/ -.. _tox: http://tox.testrun.org/ - - -Contributors ------------- - -Sergey Astanin, Pau Tallada Crespí, Erwin Marsi, Mik Kocikowski, Bill Ryder, -Zach Dwiel, Frederik Rietdijk, Philipp Bogensberger, Greg (anonymous), -Stefan Tatschner, Emiel van Miltenburg, Brandon Bennett, Amjith Ramanujam. +===============
+python-tabulate
+===============
+
+Pretty-print tabular data in Python, a library and a command-line
+utility.
+
+The main use cases of the library are:
+
+* printing small tables without hassle: just one function call,
+ formatting is guided by the data itself
+
+* authoring tabular data for lightweight plain-text markup: multiple
+ output formats suitable for further editing or transformation
+
+* readable presentation of mixed textual and numeric data: smart
+ column alignment, configurable number formatting, alignment by a
+ decimal point
+
+
+Installation
+------------
+
+To install the Python library and the command line utility, run::
+
+ pip install tabulate
+
+The command line utility will be installed as ``tabulate`` to ``bin`` on Linux
+(e.g. ``/usr/bin``); or as ``tabulate.exe`` to ``Scripts`` in your Python
+installation on Windows (e.g. ``C:\Python27\Scripts\tabulate.exe``).
+
+You may consider installing the library only for the current user::
+
+ pip install tabulate --user
+
+In this case the command line utility will be installed to ``~/.local/bin/tabulate``
+on Linux and to ``%APPDATA%\Python\Scripts\tabulate.exe`` on Windows.
+
+To install just the library on Unix-like operating systems::
+
+ TABULATE_INSTALL=lib-only pip install tabulate
+
+On Windows::
+
+ set TABULATE_INSTALL=lib-only
+ pip install tabulate
+
+
+Build status
+------------
+
+.. image:: https://drone.io/bitbucket.org/astanin/python-tabulate/status.png
+ :alt: Build status
+ :target: https://drone.io/bitbucket.org/astanin/python-tabulate/latest
+
+
+Library usage
+-------------
+
+The module provides just one function, ``tabulate``, which takes a
+list of lists or another tabular data type as the first argument,
+and outputs a nicely formatted plain-text table::
+
+ >>> from tabulate import tabulate
+
+ >>> table = [["Sun",696000,1989100000],["Earth",6371,5973.6],
+ ... ["Moon",1737,73.5],["Mars",3390,641.85]]
+ >>> print tabulate(table)
+ ----- ------ -------------
+ Sun 696000 1.9891e+09
+ Earth 6371 5973.6
+ Moon 1737 73.5
+ Mars 3390 641.85
+ ----- ------ -------------
+
+The following tabular data types are supported:
+
+* list of lists or another iterable of iterables
+* list or another iterable of dicts (keys as columns)
+* dict of iterables (keys as columns)
+* two-dimensional NumPy array
+* NumPy record arrays (names as columns)
+* pandas.DataFrame
+
+Examples in this file use Python2. Tabulate supports Python3 too.
+
+
+Headers
+~~~~~~~
+
+The second optional argument named ``headers`` defines a list of
+column headers to be used::
+
+ >>> print tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"])
+ Planet R (km) mass (x 10^29 kg)
+ -------- -------- -------------------
+ Sun 696000 1.9891e+09
+ Earth 6371 5973.6
+ Moon 1737 73.5
+ Mars 3390 641.85
+
+If ``headers="firstrow"``, then the first row of data is used::
+
+ >>> print tabulate([["Name","Age"],["Alice",24],["Bob",19]],
+ ... headers="firstrow")
+ Name Age
+ ------ -----
+ Alice 24
+ Bob 19
+
+
+If ``headers="keys"``, then the keys of a dictionary/dataframe, or
+column indices are used. It also works for NumPy record arrays and
+lists of dictionaries or named tuples::
+
+ >>> print tabulate({"Name": ["Alice", "Bob"],
+ ... "Age": [24, 19]}, headers="keys")
+ Age Name
+ ----- ------
+ 24 Alice
+ 19 Bob
+
+
+Row Indices
+~~~~~~~~~~~
+
+By default, only pandas.DataFrame tables have an additional column
+called row index. To add a similar column to any other type of table,
+pass ``showindex="always"`` or ``showindex=True`` argument to
+``tabulate()``. To suppress row indices for all types of data, pass
+``showindex="never"`` or ``showindex=False``. To add a custom row
+index column, pass ``showindex=rowIDs``, where ``rowIDs`` is some
+iterable::
+
+ >>> print(tabulate([["F",24],["M",19]], showindex="always"))
+ - - --
+ 0 F 24
+ 1 M 19
+ - - --
+
+
+Table format
+~~~~~~~~~~~~
+
+There is more than one way to format a table in plain text.
+The third optional argument named ``tablefmt`` defines
+how the table is formatted.
+
+Supported table formats are:
+
+- "plain"
+- "simple"
+- "grid"
+- "fancy_grid"
+- "pipe"
+- "orgtbl"
+- "jira"
+- "psql"
+- "rst"
+- "mediawiki"
+- "moinmoin"
+- "html"
+- "latex"
+- "latex_booktabs"
+- "textile"
+
+``plain`` tables do not use any pseudo-graphics to draw lines::
+
+ >>> table = [["spam",42],["eggs",451],["bacon",0]]
+ >>> headers = ["item", "qty"]
+ >>> print tabulate(table, headers, tablefmt="plain")
+ item qty
+ spam 42
+ eggs 451
+ bacon 0
+
+``simple`` is the default format (the default may change in future
+versions). It corresponds to ``simple_tables`` in `Pandoc Markdown
+extensions`_::
+
+ >>> print tabulate(table, headers, tablefmt="simple")
+ item qty
+ ------ -----
+ spam 42
+ eggs 451
+ bacon 0
+
+``grid`` is like tables formatted by Emacs' `table.el`_
+package. It corresponds to ``grid_tables`` in Pandoc Markdown
+extensions::
+
+ >>> print tabulate(table, headers, tablefmt="grid")
+ +--------+-------+
+ | item | qty |
+ +========+=======+
+ | spam | 42 |
+ +--------+-------+
+ | eggs | 451 |
+ +--------+-------+
+ | bacon | 0 |
+ +--------+-------+
+
+``fancy_grid`` draws a grid using box-drawing characters::
+
+ >>> print tabulate(table, headers, tablefmt="fancy_grid")
+ ╒════════╤═══════╕
+ │ item │ qty │
+ ╞════════╪═══════╡
+ │ spam │ 42 │
+ ├────────┼───────┤
+ │ eggs │ 451 │
+ ├────────┼───────┤
+ │ bacon │ 0 │
+ ╘════════╧═══════╛
+
+``psql`` is like tables formatted by Postgres' psql cli::
+
+ >>> print tabulate.tabulate()
+ +--------+-------+
+ | item | qty |
+ |--------+-------|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+ +--------+-------+
+
+``pipe`` follows the conventions of `PHP Markdown Extra`_ extension. It
+corresponds to ``pipe_tables`` in Pandoc. This format uses colons to
+indicate column alignment::
+
+ >>> print tabulate(table, headers, tablefmt="pipe")
+ | item | qty |
+ |:-------|------:|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+``orgtbl`` follows the conventions of Emacs `org-mode`_, and is editable
+also in the minor `orgtbl-mode`. Hence its name::
+
+ >>> print tabulate(table, headers, tablefmt="orgtbl")
+ | item | qty |
+ |--------+-------|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+``jira`` follows the conventions of Atlassian Jira markup language::
+
+ >>> print tabulate(table, headers, tablefmt="jira")
+ || item || qty ||
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+``rst`` formats data like a simple table of the `reStructuredText`_ format::
+
+ >>> print tabulate(table, headers, tablefmt="rst")
+ ====== =====
+ item qty
+ ====== =====
+ spam 42
+ eggs 451
+ bacon 0
+ ====== =====
+
+``mediawiki`` format produces a table markup used in `Wikipedia`_ and on
+other MediaWiki-based sites::
+
+ >>> print tabulate(table, headers, tablefmt="mediawiki")
+ {| class="wikitable" style="text-align: left;"
+ |+ <!-- caption -->
+ |-
+ ! item !! align="right"| qty
+ |-
+ | spam || align="right"| 42
+ |-
+ | eggs || align="right"| 451
+ |-
+ | bacon || align="right"| 0
+ |}
+
+``moinmoin`` format produces a table markup used in `MoinMoin`_
+wikis::
+
+ >>> print tabulate(d,headers,tablefmt="moinmoin")
+ || ''' item ''' || ''' quantity ''' ||
+ || spam || 41.999 ||
+ || eggs || 451 ||
+ || bacon || ||
+
+``textile`` format produces a table markup used in `Textile`_ format::
+
+ >>> print tabulate(table, headers, tablefmt='textile')
+ |_. item |_. qty |
+ |<. spam |>. 42 |
+ |<. eggs |>. 451 |
+ |<. bacon |>. 0 |
+
+``html`` produces standard HTML markup::
+
+ >>> print tabulate(table, headers, tablefmt="html")
+ <table>
+ <tbody>
+ <tr><th>item </th><th style="text-align: right;"> qty</th></tr>
+ <tr><td>spam </td><td style="text-align: right;"> 42</td></tr>
+ <tr><td>eggs </td><td style="text-align: right;"> 451</td></tr>
+ <tr><td>bacon </td><td style="text-align: right;"> 0</td></tr>
+ </tbody>
+ </table>
+
+``latex`` format creates a ``tabular`` environment for LaTeX markup::
+
+ >>> print tabulate(table, headers, tablefmt="latex")
+ \begin{tabular}{lr}
+ \hline
+ item & qty \\
+ \hline
+ spam & 42 \\
+ eggs & 451 \\
+ bacon & 0 \\
+ \hline
+ \end{tabular}
+
+``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup
+using spacing and style from the ``booktabs`` package.
+
+
+.. _Pandoc Markdown extensions: http://johnmacfarlane.net/pandoc/README.html#tables
+.. _PHP Markdown Extra: http://michelf.ca/projects/php-markdown/extra/#table
+.. _table.el: http://table.sourceforge.net/
+.. _org-mode: http://orgmode.org/manual/Tables.html
+.. _reStructuredText: http://docutils.sourceforge.net/docs/user/rst/quickref.html#tables
+.. _Textile: http://redcloth.org/hobix.com/textile/
+.. _Wikipedia: http://www.mediawiki.org/wiki/Help:Tables
+
+
+Column alignment
+~~~~~~~~~~~~~~~~
+
+``tabulate`` is smart about column alignment. It detects columns which
+contain only numbers, and aligns them by a decimal point (or flushes
+them to the right if they appear to be integers). Text columns are
+flushed to the left.
+
+You can override the default alignment with ``numalign`` and
+``stralign`` named arguments. Possible column alignments are:
+``right``, ``center``, ``left``, ``decimal`` (only for numbers), and
+``None`` (to disable alignment).
+
+Aligning by a decimal point works best when you need to compare
+numbers at a glance::
+
+ >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]])
+ ----------
+ 1.2345
+ 123.45
+ 12.345
+ 12345
+ 1234.5
+ ----------
+
+Compare this with a more common right alignment::
+
+ >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]], numalign="right")
+ ------
+ 1.2345
+ 123.45
+ 12.345
+ 12345
+ 1234.5
+ ------
+
+For ``tabulate``, anything which can be parsed as a number is a
+number. Even numbers represented as strings are aligned properly. This
+feature comes in handy when reading a mixed table of text and numbers
+from a file:
+
+::
+
+ >>> import csv ; from StringIO import StringIO
+ >>> table = list(csv.reader(StringIO("spam, 42\neggs, 451\n")))
+ >>> table
+ [['spam', ' 42'], ['eggs', ' 451']]
+ >>> print tabulate(table)
+ ---- ----
+ spam 42
+ eggs 451
+ ---- ----
+
+
+
+Number formatting
+~~~~~~~~~~~~~~~~~
+
+``tabulate`` allows to define custom number formatting applied to all
+columns of decimal numbers. Use ``floatfmt`` named argument::
+
+
+ >>> print tabulate([["pi",3.141593],["e",2.718282]], floatfmt=".4f")
+ -- ------
+ pi 3.1416
+ e 2.7183
+ -- ------
+
+
+Wide (fullwidth CJK) symbols
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To properly align tables which contain wide characters (typically fullwidth
+glyphs from Chinese, Japanese or Korean languages), the user should install
+``wcwidth`` library. To install it together with ``tabulate``::
+
+ pip install tabulate[widechars]
+
+Wide character support is enabled automatically if ``wcwidth`` library is
+already installed. To disable wide characters support without uninstalling
+``wcwidth``, set the global module-level flag ``WIDE_CHARS_MODE``::
+
+ import tabulate
+ tabulate.WIDE_CHARS_MODE = False
+
+
+Usage of the command line utility
+---------------------------------
+
+::
+
+ Usage: tabulate [options] [FILE ...]
+
+ FILE a filename of the file with tabular data;
+ if "-" or missing, read data from stdin.
+
+ Options:
+
+ -h, --help show this message
+ -1, --header use the first row of data as a table header
+ -o FILE, --output FILE print table to FILE (default: stdout)
+ -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace)
+ -F FPFMT, --float FPFMT floating point number format (default: g)
+ -f FMT, --format FMT set output table format; supported formats:
+ plain, simple, grid, fancy_grid, pipe, orgtbl,
+ rst, mediawiki, html, latex, latex_booktabs, tsv
+ (default: simple)
+
+
+Performance considerations
+--------------------------
+
+Such features as decimal point alignment and trying to parse everything
+as a number imply that ``tabulate``:
+
+* has to "guess" how to print a particular tabular data type
+* needs to keep the entire table in-memory
+* has to "transpose" the table twice
+* does much more work than it may appear
+
+It may not be suitable for serializing really big tables (but who's
+going to do that, anyway?) or printing tables in performance sensitive
+applications. ``tabulate`` is about two orders of magnitude slower
+than simply joining lists of values with a tab, coma or other
+separator.
+
+In the same time ``tabulate`` is comparable to other table
+pretty-printers. Given a 10x10 table (a list of lists) of mixed text
+and numeric data, ``tabulate`` appears to be slower than
+``asciitable``, and faster than ``PrettyTable`` and ``texttable``
+
+::
+
+ ================================= ========== ===========
+ Table formatter time, μs rel. time
+ ================================= ========== ===========
+ csv to StringIO 25.3 1.0
+ join with tabs and newlines 33.6 1.3
+ asciitable (0.8.0) 590.0 23.4
+ tabulate (0.7.7) 1403.5 55.6
+ tabulate (0.7.7, WIDE_CHARS_MODE) 2156.6 85.4
+ PrettyTable (0.7.2) 3377.0 133.7
+ texttable (0.8.6) 3986.3 157.8
+ ================================= ========== ===========
+
+
+Version history
+---------------
+
+- 0.8: FUTURE RELEASE
+- 0.7.6: Bug fixes. New table formats (``psql``, ``jira``, ``moinmoin``, ``textile``).
+ Wide character support. Printing from database cursors.
+ Option to print row indices. Boolean columns. Ragged rows.
+ Option to disable number parsing.
+- 0.7.5: Bug fixes. ``--float`` format option for the command line utility.
+- 0.7.4: Bug fixes. ``fancy_grid`` and ``html`` formats. Command line utility.
+- 0.7.3: Bug fixes. Python 3.4 support. Iterables of dicts. ``latex_booktabs`` format.
+- 0.7.2: Python 3.2 support.
+- 0.7.1: Bug fixes. ``tsv`` format. Column alignment can be disabled.
+- 0.7: ``latex`` tables. Printing lists of named tuples and NumPy
+ record arrays. Fix printing date and time values. Python <= 2.6.4 is supported.
+- 0.6: ``mediawiki`` tables, bug fixes.
+- 0.5.1: Fix README.rst formatting. Optimize (performance similar to 0.4.4).
+- 0.5: ANSI color sequences. Printing dicts of iterables and Pandas' dataframes.
+- 0.4.4: Python 2.6 support.
+- 0.4.3: Bug fix, None as a missing value.
+- 0.4.2: Fix manifest file.
+- 0.4.1: Update license and documentation.
+- 0.4: Unicode support, Python3 support, ``rst`` tables.
+- 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``,
+ ``grid``, ``pipe``, and ``orgtbl``.
+
+
+How to contribute
+-----------------
+
+Contributions should include tests and an explanation for the changes they
+propose. Documentation (examples, docstrings, README.rst) should be updated
+accordingly.
+
+This project uses `nose`_ testing framework and `tox`_ to automate testing in
+different environments. Add tests to one of the files in the ``test/`` folder.
+
+To run tests on all supported Python versions, make sure all Python
+interpreters, ``nose`` and ``tox`` are installed, then run ``tox`` in
+the root of the project source tree.
+
+On Linux ``tox`` expects to find executables like ``python2.6``,
+``python2.7``, ``python3.4`` etc. On Windows it looks for
+``C:\Python26\python.exe``, ``C:\Python27\python.exe`` and
+``C:\Python34\python.exe`` respectively.
+
+To test only some Python environements, use ``-e`` option. For
+example, to test only against Python 2.7 and Python 3.4, run::
+
+ tox -e py27,py34
+
+in the root of the project source tree.
+
+To enable NumPy and Pandas tests, run::
+
+ tox -e py27-extra,py34-extra
+
+(this may take a long time the first time, because NumPy and Pandas
+will have to be installed in the new virtual environments)
+
+See ``tox.ini`` file to learn how to use ``nosetests`` directly to
+test individual Python versions.
+
+.. _nose: https://nose.readthedocs.org/
+.. _tox: http://tox.testrun.org/
+
+
+Contributors
+------------
+
+Sergey Astanin, Pau Tallada Crespí, Erwin Marsi, Mik Kocikowski, Bill Ryder,
+Zach Dwiel, Frederik Rietdijk, Philipp Bogensberger, Greg (anonymous),
+Stefan Tatschner, Emiel van Miltenburg, Brandon Bennett, Amjith Ramanujam,
+Jan Schulz, Simon Percivall, Javier Santacruz López-Cepero, Sam Denton,
+Alexey Ziyangirov, acaird, Cesar Sanchez, naught101, John Vandenberg,
+Zack Dever.
diff --git a/benchmark.py b/benchmark.py deleted file mode 100644 index b92da47..0000000 --- a/benchmark.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from timeit import timeit -import tabulate -import asciitable -import prettytable -import texttable -import sys -import codecs - -setup_code = r""" -from csv import writer -from StringIO import StringIO -import tabulate -import asciitable -import prettytable -import texttable -import tabletext - - -table=[["some text"]+range(i,i+9) for i in range(10)] - - -def csv_table(table): - buf = StringIO() - writer(buf).writerows(table) - return buf.getvalue() - - -def join_table(table): - return "\n".join(("\t".join(map(str,row)) for row in table)) - - -def run_prettytable(table): - pp = prettytable.PrettyTable() - for row in table: - pp.add_row(row) - return str(pp) - - -def run_asciitable(table): - buf = StringIO() - asciitable.write(table, output=buf, Writer=asciitable.FixedWidth) - return buf.getvalue() - - -def run_texttable(table): - pp = texttable.Texttable() - pp.set_cols_align(["l"] + ["r"]*9) - pp.add_rows(table) - return pp.draw() - - -def run_tabletext(table): - return tabletext.to_text(table) - - -def run_tabulate(table): - return tabulate.tabulate(table) - - -""" - -methods = [(u"join with tabs and newlines", "join_table(table)"), - (u"csv to StringIO", "csv_table(table)"), - (u"asciitable (%s)" % asciitable.__version__, "run_asciitable(table)"), - (u"tabulate (%s)" % tabulate.__version__, "run_tabulate(table)"), - (u"PrettyTable (%s)" % prettytable.__version__, "run_prettytable(table)"), - (u"texttable (%s)" % texttable.__version__, "run_texttable(table)"), - (u"tabletext (0.1)", "run_tabletext(table)"), - ] - - -def benchmark(n): - global methods - if '--onlyself' in sys.argv[1:]: - methods = [ m for m in methods if m[0].startswith("tabulate") ] - else: - methods = methods - - results = [(desc, timeit(code, setup_code, number=n)/n * 1e6) - for desc, code in methods] - mintime = min(map(lambda x: x[1], results)) - results = [(desc, t, t/mintime) for desc, t in - sorted(results, key=lambda x: x[1])] - table = tabulate.tabulate(results, - [u"Table formatter", u"time, μs", u"rel. time"], - u"rst", floatfmt=".1f") - print codecs.encode(table, "utf-8") - - -if __name__ == "__main__": - if sys.argv[1:]: - n = int(sys.argv[1]) - else: - n = 10000 - benchmark(n) diff --git a/debian/.git-dpm b/debian/.git-dpm new file mode 100644 index 0000000..069d6cd --- /dev/null +++ b/debian/.git-dpm @@ -0,0 +1,11 @@ +# see git-dpm(1) from git-dpm package +3aa23bd9f6b7172b47447c192a6f09137fd6d8e8 +3aa23bd9f6b7172b47447c192a6f09137fd6d8e8 +3aa23bd9f6b7172b47447c192a6f09137fd6d8e8 +3aa23bd9f6b7172b47447c192a6f09137fd6d8e8 +python-tabulate_0.7.7.orig.tar.gz +9021b475eefb357deb12f7b0e59a12457411472c +39140 +debianTag="debian/%e%v" +patchedTag="patched/%e%v" +upstreamTag="upstream/%e%u" diff --git a/debian/changelog b/debian/changelog index 07652ec..8510e83 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,25 @@ +python-tabulate (0.7.7-1) unstable; urgency=medium + + [ Sandro Tosi ] + * New upstream release + * debian/control + - adjust Vcs-Browser to DPMT standards + * debian/control + - update upstream copyright years + - add copyright for my contributions + - add wcwidth to b-d + - bump Standards-Version to 3.9.8 (no changes needed) + * debian/python-tabulate.examples + - dont install benchmarl.py in py2 package, remove from the tarball + * debian/rules + - dont run tests (for now) + * compat level 10 + + [ Ondřej Nový ] + * Fixed VCS URL (https) + + -- Sandro Tosi <morph@debian.org> Fri, 16 Dec 2016 19:37:17 -0500 + python-tabulate (0.7.5-1) unstable; urgency=low * Initial release (Closes: #782140) diff --git a/debian/compat b/debian/compat index ec63514..f599e28 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -9 +10 diff --git a/debian/control b/debian/control index 71b6838..0c7ed52 100644 --- a/debian/control +++ b/debian/control @@ -1,9 +1,9 @@ Source: python-tabulate Priority: optional Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org> -Uploaders: ChangZhuo Chen (陳昌倬) <czchen@gmail.com>, +Uploaders: ChangZhuo Chen (陳昌倬) <czchen@debian.org>, Sandro Tosi <morph@debian.org> -Build-Depends: debhelper (>= 9), +Build-Depends: debhelper (>= 10), dh-python, python3-all, python3-nose, @@ -11,11 +11,13 @@ Build-Depends: debhelper (>= 9), python-all (>= 2.6.6-3~), python-nose, python-setuptools, -Standards-Version: 3.9.6 + python-wcwidth, + python3-wcwidth, +Standards-Version: 3.9.8 Section: python Homepage: https://bitbucket.org/astanin/python-tabulate/ -Vcs-Git: git://anonscm.debian.org/python-modules/packages/python-tabulate.git -Vcs-Browser: http://anonscm.debian.org/cgit/python-modules/packages/python-tabulate.git +Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/python-tabulate.git +Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/python-tabulate.git X-Python-Version: >= 2.6 X-Python3-Version: >= 3.2 diff --git a/debian/copyright b/debian/copyright index b2f2de6..916497f 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,11 +3,12 @@ Upstream-Name: python-tabulate Source: https://bitbucket.org/astanin/python-tabulate Files: * -Copyright: Copyright (c) 2011-2014 Sergey Astanin <s.astanin@gmail.com> +Copyright: Copyright (c) 2011-2016 Sergey Astanin <s.astanin@gmail.com> License: Expat Files: debian/* -Copyright: 2015 ChangZhuo Chen (陳昌倬) <czchen@gmail.com> +Copyright: 2015 ChangZhuo Chen (陳昌倬) <czchen@debian.org> + 2016 Sandro Tosi <morph@debian.org> License: Expat License: Expat diff --git a/debian/python-tabulate.examples b/debian/python-tabulate.examples index 1c5dd73..81b8af1 100644 --- a/debian/python-tabulate.examples +++ b/debian/python-tabulate.examples @@ -1 +1 @@ -benchmark.py +#benchmark.py diff --git a/debian/rules b/debian/rules index d5ce168..4645606 100755 --- a/debian/rules +++ b/debian/rules @@ -9,3 +9,6 @@ override_dh_install: dh_install # Install executable in python3-tabulate only to avoid name collision. rm -rf $(CURDIR)/debian/python-tabulate/usr/bin + +override_dh_auto_test: + echo "https://bitbucket.org/astanin/python-tabulate/issues/106/test-commonpy-not-release-in-pypi-tarball" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..b14b0bc --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
@@ -1,55 +1,63 @@ -#!/usr/bin/env python - -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - - -from platform import python_version_tuple -import os -import re - - -LICENSE = open("LICENSE").read() - - -# strip links from the descripton on the PyPI -if python_version_tuple()[0] >= '3': - LONG_DESCRIPTION = open("README.rst", "r", encoding="utf-8").read().replace("`_", "`") -else: - LONG_DESCRIPTION = open("README.rst", "r").read().replace("`_", "`") - -# strip Build Status from the PyPI package -if python_version_tuple()[:2] >= ('2', '7'): - LONG_DESCRIPTION = re.sub("^Build status\n(.*\n){7}", "", LONG_DESCRIPTION, flags=re.M) - - -install_options = os.environ.get("TABULATE_INSTALL","").split(",") -libonly_flags = set(["lib-only", "libonly", "no-cli", "without-cli"]) -if libonly_flags.intersection(install_options): - console_scripts = [] -else: - console_scripts = ['tabulate = tabulate:_main'] - - -setup(name='tabulate', - version='0.7.5', - description='Pretty-print tabular data', - long_description=LONG_DESCRIPTION, - author='Sergey Astanin', - author_email='s.astanin@gmail.com', - url='https://bitbucket.org/astanin/python-tabulate', - license=LICENSE, - classifiers= [ "Development Status :: 4 - Beta", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Topic :: Software Development :: Libraries" ], - py_modules = ['tabulate'], - entry_points = {'console_scripts': console_scripts}, - test_suite = 'nose.collector') +#!/usr/bin/env python
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+
+from platform import python_version_tuple, python_implementation
+import os
+import re
+
+
+LICENSE = open("LICENSE").read()
+
+
+# strip links from the descripton on the PyPI
+if python_version_tuple()[0] >= '3':
+ LONG_DESCRIPTION = open("README.rst", "r", encoding="utf-8").read().replace("`_", "`")
+else:
+ LONG_DESCRIPTION = open("README.rst", "r").read().replace("`_", "`")
+
+# strip Build Status from the PyPI package
+try:
+ if python_version_tuple()[:2] >= ('2', '7'):
+ status_re = "^Build status\n(.*\n){7}"
+ LONG_DESCRIPTION = re.sub(status_re, "", LONG_DESCRIPTION, flags=re.M)
+except TypeError:
+ if python_implementation() == "IronPython":
+ # IronPython doesn't support flags in re.sub (IronPython issue #923)
+ pass
+ else:
+ raise
+
+install_options = os.environ.get("TABULATE_INSTALL","").split(",")
+libonly_flags = set(["lib-only", "libonly", "no-cli", "without-cli"])
+if libonly_flags.intersection(install_options):
+ console_scripts = []
+else:
+ console_scripts = ['tabulate = tabulate:_main']
+
+
+setup(name='tabulate',
+ version='0.7.7',
+ description='Pretty-print tabular data',
+ long_description=LONG_DESCRIPTION,
+ author='Sergey Astanin',
+ author_email='s.astanin@gmail.com',
+ url='https://bitbucket.org/astanin/python-tabulate',
+ license=LICENSE,
+ classifiers= [ "Development Status :: 4 - Beta",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.2",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Topic :: Software Development :: Libraries" ],
+ py_modules = ['tabulate'],
+ entry_points = {'console_scripts': console_scripts},
+ extras_require = {'widechars': ['wcwidth']},
+ test_suite = 'nose.collector')
diff --git a/tabulate.egg-info/.PKG-INFO.swp b/tabulate.egg-info/.PKG-INFO.swp Binary files differnew file mode 100644 index 0000000..f01081e --- /dev/null +++ b/tabulate.egg-info/.PKG-INFO.swp diff --git a/tabulate.egg-info/PKG-INFO b/tabulate.egg-info/PKG-INFO new file mode 100644 index 0000000..dda9777 --- /dev/null +++ b/tabulate.egg-info/PKG-INFO @@ -0,0 +1,590 @@ +Metadata-Version: 1.1
+Name: tabulate
+Version: 0.7.7
+Summary: Pretty-print tabular data
+Home-page: https://bitbucket.org/astanin/python-tabulate
+Author: Sergey Astanin
+Author-email: s.astanin@gmail.com
+License: Copyright (c) 2011-2016 Sergey Astanin
+
+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.
+
+Description: ===============
+ python-tabulate
+ ===============
+
+ Pretty-print tabular data in Python, a library and a command-line
+ utility.
+
+ The main use cases of the library are:
+
+ * printing small tables without hassle: just one function call,
+ formatting is guided by the data itself
+
+ * authoring tabular data for lightweight plain-text markup: multiple
+ output formats suitable for further editing or transformation
+
+ * readable presentation of mixed textual and numeric data: smart
+ column alignment, configurable number formatting, alignment by a
+ decimal point
+
+
+ Installation
+ ------------
+
+ To install the Python library and the command line utility, run::
+
+ pip install tabulate
+
+ The command line utility will be installed as ``tabulate`` to ``bin`` on Linux
+ (e.g. ``/usr/bin``); or as ``tabulate.exe`` to ``Scripts`` in your Python
+ installation on Windows (e.g. ``C:\Python27\Scripts\tabulate.exe``).
+
+ You may consider installing the library only for the current user::
+
+ pip install tabulate --user
+
+ In this case the command line utility will be installed to ``~/.local/bin/tabulate``
+ on Linux and to ``%APPDATA%\Python\Scripts\tabulate.exe`` on Windows.
+
+ To install just the library on Unix-like operating systems::
+
+ TABULATE_INSTALL=lib-only pip install tabulate
+
+ On Windows::
+
+ set TABULATE_INSTALL=lib-only
+ pip install tabulate
+
+
+ Library usage
+ -------------
+
+ The module provides just one function, ``tabulate``, which takes a
+ list of lists or another tabular data type as the first argument,
+ and outputs a nicely formatted plain-text table::
+
+ >>> from tabulate import tabulate
+
+ >>> table = [["Sun",696000,1989100000],["Earth",6371,5973.6],
+ ... ["Moon",1737,73.5],["Mars",3390,641.85]]
+ >>> print tabulate(table)
+ ----- ------ -------------
+ Sun 696000 1.9891e+09
+ Earth 6371 5973.6
+ Moon 1737 73.5
+ Mars 3390 641.85
+ ----- ------ -------------
+
+ The following tabular data types are supported:
+
+ * list of lists or another iterable of iterables
+ * list or another iterable of dicts (keys as columns)
+ * dict of iterables (keys as columns)
+ * two-dimensional NumPy array
+ * NumPy record arrays (names as columns)
+ * pandas.DataFrame
+
+ Examples in this file use Python2. Tabulate supports Python3 too.
+
+
+ Headers
+ ~~~~~~~
+
+ The second optional argument named ``headers`` defines a list of
+ column headers to be used::
+
+ >>> print tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"])
+ Planet R (km) mass (x 10^29 kg)
+ -------- -------- -------------------
+ Sun 696000 1.9891e+09
+ Earth 6371 5973.6
+ Moon 1737 73.5
+ Mars 3390 641.85
+
+ If ``headers="firstrow"``, then the first row of data is used::
+
+ >>> print tabulate([["Name","Age"],["Alice",24],["Bob",19]],
+ ... headers="firstrow")
+ Name Age
+ ------ -----
+ Alice 24
+ Bob 19
+
+
+ If ``headers="keys"``, then the keys of a dictionary/dataframe, or
+ column indices are used. It also works for NumPy record arrays and
+ lists of dictionaries or named tuples::
+
+ >>> print tabulate({"Name": ["Alice", "Bob"],
+ ... "Age": [24, 19]}, headers="keys")
+ Age Name
+ ----- ------
+ 24 Alice
+ 19 Bob
+
+
+ Row Indices
+ ~~~~~~~~~~~
+
+ By default, only pandas.DataFrame tables have an additional column
+ called row index. To add a similar column to any other type of table,
+ pass ``showindex="always"`` or ``showindex=True`` argument to
+ ``tabulate()``. To suppress row indices for all types of data, pass
+ ``showindex="never"`` or ``showindex=False``. To add a custom row
+ index column, pass ``showindex=rowIDs``, where ``rowIDs`` is some
+ iterable::
+
+ >>> print(tabulate([["F",24],["M",19]], showindex="always"))
+ - - --
+ 0 F 24
+ 1 M 19
+ - - --
+
+
+ Table format
+ ~~~~~~~~~~~~
+
+ There is more than one way to format a table in plain text.
+ The third optional argument named ``tablefmt`` defines
+ how the table is formatted.
+
+ Supported table formats are:
+
+ - "plain"
+ - "simple"
+ - "grid"
+ - "fancy_grid"
+ - "pipe"
+ - "orgtbl"
+ - "jira"
+ - "psql"
+ - "rst"
+ - "mediawiki"
+ - "moinmoin"
+ - "html"
+ - "latex"
+ - "latex_booktabs"
+ - "textile"
+
+ ``plain`` tables do not use any pseudo-graphics to draw lines::
+
+ >>> table = [["spam",42],["eggs",451],["bacon",0]]
+ >>> headers = ["item", "qty"]
+ >>> print tabulate(table, headers, tablefmt="plain")
+ item qty
+ spam 42
+ eggs 451
+ bacon 0
+
+ ``simple`` is the default format (the default may change in future
+ versions). It corresponds to ``simple_tables`` in `Pandoc Markdown
+ extensions`::
+
+ >>> print tabulate(table, headers, tablefmt="simple")
+ item qty
+ ------ -----
+ spam 42
+ eggs 451
+ bacon 0
+
+ ``grid`` is like tables formatted by Emacs' `table.el`
+ package. It corresponds to ``grid_tables`` in Pandoc Markdown
+ extensions::
+
+ >>> print tabulate(table, headers, tablefmt="grid")
+ +--------+-------+
+ | item | qty |
+ +========+=======+
+ | spam | 42 |
+ +--------+-------+
+ | eggs | 451 |
+ +--------+-------+
+ | bacon | 0 |
+ +--------+-------+
+
+ ``fancy_grid`` draws a grid using box-drawing characters::
+
+ >>> print tabulate(table, headers, tablefmt="fancy_grid")
+ ╒════════╤═══════╕
+ │ item │ qty │
+ ╞════════╪═══════╡
+ │ spam │ 42 │
+ ├────────┼───────┤
+ │ eggs │ 451 │
+ ├────────┼───────┤
+ │ bacon │ 0 │
+ ╘════════╧═══════╛
+
+ ``psql`` is like tables formatted by Postgres' psql cli::
+
+ >>> print tabulate.tabulate()
+ +--------+-------+
+ | item | qty |
+ |--------+-------|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+ +--------+-------+
+
+ ``pipe`` follows the conventions of `PHP Markdown Extra` extension. It
+ corresponds to ``pipe_tables`` in Pandoc. This format uses colons to
+ indicate column alignment::
+
+ >>> print tabulate(table, headers, tablefmt="pipe")
+ | item | qty |
+ |:-------|------:|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+ ``orgtbl`` follows the conventions of Emacs `org-mode`, and is editable
+ also in the minor `orgtbl-mode`. Hence its name::
+
+ >>> print tabulate(table, headers, tablefmt="orgtbl")
+ | item | qty |
+ |--------+-------|
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+ ``jira`` follows the conventions of Atlassian Jira markup language::
+
+ >>> print tabulate(table, headers, tablefmt="jira")
+ || item || qty ||
+ | spam | 42 |
+ | eggs | 451 |
+ | bacon | 0 |
+
+ ``rst`` formats data like a simple table of the `reStructuredText` format::
+
+ >>> print tabulate(table, headers, tablefmt="rst")
+ ====== =====
+ item qty
+ ====== =====
+ spam 42
+ eggs 451
+ bacon 0
+ ====== =====
+
+ ``mediawiki`` format produces a table markup used in `Wikipedia` and on
+ other MediaWiki-based sites::
+
+ >>> print tabulate(table, headers, tablefmt="mediawiki")
+ {| class="wikitable" style="text-align: left;"
+ |+ <!-- caption -->
+ |-
+ ! item !! align="right"| qty
+ |-
+ | spam || align="right"| 42
+ |-
+ | eggs || align="right"| 451
+ |-
+ | bacon || align="right"| 0
+ |}
+
+ ``moinmoin`` format produces a table markup used in `MoinMoin`
+ wikis::
+
+ >>> print tabulate(d,headers,tablefmt="moinmoin")
+ || ''' item ''' || ''' quantity ''' ||
+ || spam || 41.999 ||
+ || eggs || 451 ||
+ || bacon || ||
+
+ ``textile`` format produces a table markup used in `Textile` format::
+
+ >>> print tabulate(table, headers, tablefmt='textile')
+ |_. item |_. qty |
+ |<. spam |>. 42 |
+ |<. eggs |>. 451 |
+ |<. bacon |>. 0 |
+
+ ``html`` produces standard HTML markup::
+
+ >>> print tabulate(table, headers, tablefmt="html")
+ <table>
+ <tbody>
+ <tr><th>item </th><th style="text-align: right;"> qty</th></tr>
+ <tr><td>spam </td><td style="text-align: right;"> 42</td></tr>
+ <tr><td>eggs </td><td style="text-align: right;"> 451</td></tr>
+ <tr><td>bacon </td><td style="text-align: right;"> 0</td></tr>
+ </tbody>
+ </table>
+
+ ``latex`` format creates a ``tabular`` environment for LaTeX markup::
+
+ >>> print tabulate(table, headers, tablefmt="latex")
+ \begin{tabular}{lr}
+ \hline
+ item & qty \\
+ \hline
+ spam & 42 \\
+ eggs & 451 \\
+ bacon & 0 \\
+ \hline
+ \end{tabular}
+
+ ``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup
+ using spacing and style from the ``booktabs`` package.
+
+
+ .. _Pandoc Markdown extensions: http://johnmacfarlane.net/pandoc/README.html#tables
+ .. _PHP Markdown Extra: http://michelf.ca/projects/php-markdown/extra/#table
+ .. _table.el: http://table.sourceforge.net/
+ .. _org-mode: http://orgmode.org/manual/Tables.html
+ .. _reStructuredText: http://docutils.sourceforge.net/docs/user/rst/quickref.html#tables
+ .. _Textile: http://redcloth.org/hobix.com/textile/
+ .. _Wikipedia: http://www.mediawiki.org/wiki/Help:Tables
+
+
+ Column alignment
+ ~~~~~~~~~~~~~~~~
+
+ ``tabulate`` is smart about column alignment. It detects columns which
+ contain only numbers, and aligns them by a decimal point (or flushes
+ them to the right if they appear to be integers). Text columns are
+ flushed to the left.
+
+ You can override the default alignment with ``numalign`` and
+ ``stralign`` named arguments. Possible column alignments are:
+ ``right``, ``center``, ``left``, ``decimal`` (only for numbers), and
+ ``None`` (to disable alignment).
+
+ Aligning by a decimal point works best when you need to compare
+ numbers at a glance::
+
+ >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]])
+ ----------
+ 1.2345
+ 123.45
+ 12.345
+ 12345
+ 1234.5
+ ----------
+
+ Compare this with a more common right alignment::
+
+ >>> print tabulate([[1.2345],[123.45],[12.345],[12345],[1234.5]], numalign="right")
+ ------
+ 1.2345
+ 123.45
+ 12.345
+ 12345
+ 1234.5
+ ------
+
+ For ``tabulate``, anything which can be parsed as a number is a
+ number. Even numbers represented as strings are aligned properly. This
+ feature comes in handy when reading a mixed table of text and numbers
+ from a file:
+
+ ::
+
+ >>> import csv ; from StringIO import StringIO
+ >>> table = list(csv.reader(StringIO("spam, 42\neggs, 451\n")))
+ >>> table
+ [['spam', ' 42'], ['eggs', ' 451']]
+ >>> print tabulate(table)
+ ---- ----
+ spam 42
+ eggs 451
+ ---- ----
+
+
+
+ Number formatting
+ ~~~~~~~~~~~~~~~~~
+
+ ``tabulate`` allows to define custom number formatting applied to all
+ columns of decimal numbers. Use ``floatfmt`` named argument::
+
+
+ >>> print tabulate([["pi",3.141593],["e",2.718282]], floatfmt=".4f")
+ -- ------
+ pi 3.1416
+ e 2.7183
+ -- ------
+
+
+ Wide (fullwidth CJK) symbols
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ To properly align tables which contain wide characters (typically fullwidth
+ glyphs from Chinese, Japanese or Korean languages), the user should install
+ ``wcwidth`` library. To install it together with ``tabulate``::
+
+ pip install tabulate[widechars]
+
+ Wide character support is enabled automatically if ``wcwidth`` library is
+ already installed. To disable wide characters support without uninstalling
+ ``wcwidth``, set the global module-level flag ``WIDE_CHARS_MODE``::
+
+ import tabulate
+ tabulate.WIDE_CHARS_MODE = False
+
+
+ Usage of the command line utility
+ ---------------------------------
+
+ ::
+
+ Usage: tabulate [options] [FILE ...]
+
+ FILE a filename of the file with tabular data;
+ if "-" or missing, read data from stdin.
+
+ Options:
+
+ -h, --help show this message
+ -1, --header use the first row of data as a table header
+ -o FILE, --output FILE print table to FILE (default: stdout)
+ -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace)
+ -F FPFMT, --float FPFMT floating point number format (default: g)
+ -f FMT, --format FMT set output table format; supported formats:
+ plain, simple, grid, fancy_grid, pipe, orgtbl,
+ rst, mediawiki, html, latex, latex_booktabs, tsv
+ (default: simple)
+
+
+ Performance considerations
+ --------------------------
+
+ Such features as decimal point alignment and trying to parse everything
+ as a number imply that ``tabulate``:
+
+ * has to "guess" how to print a particular tabular data type
+ * needs to keep the entire table in-memory
+ * has to "transpose" the table twice
+ * does much more work than it may appear
+
+ It may not be suitable for serializing really big tables (but who's
+ going to do that, anyway?) or printing tables in performance sensitive
+ applications. ``tabulate`` is about two orders of magnitude slower
+ than simply joining lists of values with a tab, coma or other
+ separator.
+
+ In the same time ``tabulate`` is comparable to other table
+ pretty-printers. Given a 10x10 table (a list of lists) of mixed text
+ and numeric data, ``tabulate`` appears to be slower than
+ ``asciitable``, and faster than ``PrettyTable`` and ``texttable``
+
+ ::
+
+ ================================= ========== ===========
+ Table formatter time, μs rel. time
+ ================================= ========== ===========
+ csv to StringIO 25.3 1.0
+ join with tabs and newlines 33.6 1.3
+ asciitable (0.8.0) 590.0 23.4
+ tabulate (0.7.7) 1403.5 55.6
+ tabulate (0.7.7, WIDE_CHARS_MODE) 2156.6 85.4
+ PrettyTable (0.7.2) 3377.0 133.7
+ texttable (0.8.6) 3986.3 157.8
+ ================================= ========== ===========
+
+
+ Version history
+ ---------------
+
+ - 0.8: FUTURE RELEASE
+ - 0.7.6: Bug fixes. New table formats (``psql``, ``jira``, ``moinmoin``, ``textile``).
+ Wide character support. Printing from database cursors.
+ Option to print row indices. Boolean columns. Ragged rows.
+ Option to disable number parsing.
+ - 0.7.5: Bug fixes. ``--float`` format option for the command line utility.
+ - 0.7.4: Bug fixes. ``fancy_grid`` and ``html`` formats. Command line utility.
+ - 0.7.3: Bug fixes. Python 3.4 support. Iterables of dicts. ``latex_booktabs`` format.
+ - 0.7.2: Python 3.2 support.
+ - 0.7.1: Bug fixes. ``tsv`` format. Column alignment can be disabled.
+ - 0.7: ``latex`` tables. Printing lists of named tuples and NumPy
+ record arrays. Fix printing date and time values. Python <= 2.6.4 is supported.
+ - 0.6: ``mediawiki`` tables, bug fixes.
+ - 0.5.1: Fix README.rst formatting. Optimize (performance similar to 0.4.4).
+ - 0.5: ANSI color sequences. Printing dicts of iterables and Pandas' dataframes.
+ - 0.4.4: Python 2.6 support.
+ - 0.4.3: Bug fix, None as a missing value.
+ - 0.4.2: Fix manifest file.
+ - 0.4.1: Update license and documentation.
+ - 0.4: Unicode support, Python3 support, ``rst`` tables.
+ - 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``,
+ ``grid``, ``pipe``, and ``orgtbl``.
+
+
+ How to contribute
+ -----------------
+
+ Contributions should include tests and an explanation for the changes they
+ propose. Documentation (examples, docstrings, README.rst) should be updated
+ accordingly.
+
+ This project uses `nose` testing framework and `tox` to automate testing in
+ different environments. Add tests to one of the files in the ``test/`` folder.
+
+ To run tests on all supported Python versions, make sure all Python
+ interpreters, ``nose`` and ``tox`` are installed, then run ``tox`` in
+ the root of the project source tree.
+
+ On Linux ``tox`` expects to find executables like ``python2.6``,
+ ``python2.7``, ``python3.4`` etc. On Windows it looks for
+ ``C:\Python26\python.exe``, ``C:\Python27\python.exe`` and
+ ``C:\Python34\python.exe`` respectively.
+
+ To test only some Python environements, use ``-e`` option. For
+ example, to test only against Python 2.7 and Python 3.4, run::
+
+ tox -e py27,py34
+
+ in the root of the project source tree.
+
+ To enable NumPy and Pandas tests, run::
+
+ tox -e py27-extra,py34-extra
+
+ (this may take a long time the first time, because NumPy and Pandas
+ will have to be installed in the new virtual environments)
+
+ See ``tox.ini`` file to learn how to use ``nosetests`` directly to
+ test individual Python versions.
+
+ .. _nose: https://nose.readthedocs.org/
+ .. _tox: http://tox.testrun.org/
+
+
+ Contributors
+ ------------
+
+ Sergey Astanin, Pau Tallada Crespí, Erwin Marsi, Mik Kocikowski, Bill Ryder,
+ Zach Dwiel, Frederik Rietdijk, Philipp Bogensberger, Greg (anonymous),
+ Stefan Tatschner, Emiel van Miltenburg, Brandon Bennett, Amjith Ramanujam,
+ Jan Schulz, Simon Percivall, Javier Santacruz López-Cepero, Sam Denton,
+ Alexey Ziyangirov, acaird, Cesar Sanchez, naught101, John Vandenberg,
+ Zack Dever.
+
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Topic :: Software Development :: Libraries
diff --git a/tabulate.egg-info/SOURCES.txt b/tabulate.egg-info/SOURCES.txt new file mode 100644 index 0000000..c8cb200 --- /dev/null +++ b/tabulate.egg-info/SOURCES.txt @@ -0,0 +1,18 @@ +LICENSE +MANIFEST.in +README +README.rst +setup.py +tabulate.py +tabulate.egg-info/.PKG-INFO.swp +tabulate.egg-info/PKG-INFO +tabulate.egg-info/SOURCES.txt +tabulate.egg-info/dependency_links.txt +tabulate.egg-info/entry_points.txt +tabulate.egg-info/requires.txt +tabulate.egg-info/top_level.txt +test/test_api.py +test/test_cli.py +test/test_input.py +test/test_output.py +test/test_regression.py
\ No newline at end of file diff --git a/tabulate.egg-info/dependency_links.txt b/tabulate.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tabulate.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/tabulate.egg-info/entry_points.txt b/tabulate.egg-info/entry_points.txt new file mode 100644 index 0000000..29393b6 --- /dev/null +++ b/tabulate.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +tabulate = tabulate:_main + diff --git a/tabulate.egg-info/requires.txt b/tabulate.egg-info/requires.txt new file mode 100644 index 0000000..c4971cc --- /dev/null +++ b/tabulate.egg-info/requires.txt @@ -0,0 +1,3 @@ + +[widechars] +wcwidth diff --git a/tabulate.egg-info/top_level.txt b/tabulate.egg-info/top_level.txt new file mode 100644 index 0000000..a5d5159 --- /dev/null +++ b/tabulate.egg-info/top_level.txt @@ -0,0 +1 @@ +tabulate diff --git a/tabulate.py b/tabulate.py index 31d92d0..de81033 100644 --- a/tabulate.py +++ b/tabulate.py @@ -1,1087 +1,1333 @@ -# -*- coding: utf-8 -*- - -"""Pretty-print tabular data.""" - -from __future__ import print_function -from __future__ import unicode_literals -from collections import namedtuple -from platform import python_version_tuple -import re - - -if python_version_tuple()[0] < "3": - from itertools import izip_longest - from functools import partial - _none_type = type(None) - _int_type = int - _long_type = long - _float_type = float - _text_type = unicode - _binary_type = str - - def _is_file(f): - return isinstance(f, file) - -else: - from itertools import zip_longest as izip_longest - from functools import reduce, partial - _none_type = type(None) - _int_type = int - _long_type = int - _float_type = float - _text_type = str - _binary_type = bytes - - import io - def _is_file(f): - return isinstance(f, io.IOBase) - - -__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"] -__version__ = "0.7.5" - - -MIN_PADDING = 2 - - -Line = namedtuple("Line", ["begin", "hline", "sep", "end"]) - - -DataRow = namedtuple("DataRow", ["begin", "sep", "end"]) - - -# A table structure is suppposed to be: -# -# --- lineabove --------- -# headerrow -# --- linebelowheader --- -# datarow -# --- linebewteenrows --- -# ... (more datarows) ... -# --- linebewteenrows --- -# last datarow -# --- linebelow --------- -# -# TableFormat's line* elements can be -# -# - either None, if the element is not used, -# - or a Line tuple, -# - or a function: [col_widths], [col_alignments] -> string. -# -# TableFormat's *row elements can be -# -# - either None, if the element is not used, -# - or a DataRow tuple, -# - or a function: [cell_values], [col_widths], [col_alignments] -> string. -# -# padding (an integer) is the amount of white space around data values. -# -# with_header_hide: -# -# - either None, to display all table elements unconditionally, -# - or a list of elements not to be displayed if the table has column headers. -# -TableFormat = namedtuple("TableFormat", ["lineabove", "linebelowheader", - "linebetweenrows", "linebelow", - "headerrow", "datarow", - "padding", "with_header_hide"]) - - -def _pipe_segment_with_colons(align, colwidth): - """Return a segment of a horizontal line with optional colons which - indicate column's alignment (as in `pipe` output format).""" - w = colwidth - if align in ["right", "decimal"]: - return ('-' * (w - 1)) + ":" - elif align == "center": - return ":" + ('-' * (w - 2)) + ":" - elif align == "left": - return ":" + ('-' * (w - 1)) - else: - return '-' * w - - -def _pipe_line_with_colons(colwidths, colaligns): - """Return a horizontal line with optional colons to indicate column's - alignment (as in `pipe` output format).""" - segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)] - return "|" + "|".join(segments) + "|" - - -def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns): - alignment = { "left": '', - "right": 'align="right"| ', - "center": 'align="center"| ', - "decimal": 'align="right"| ' } - # hard-coded padding _around_ align attribute and value together - # rather than padding parameter which affects only the value - values_with_attrs = [' ' + alignment.get(a, '') + c + ' ' - for c, a in zip(cell_values, colaligns)] - colsep = separator*2 - return (separator + colsep.join(values_with_attrs)).rstrip() - - -def _html_row_with_attrs(celltag, cell_values, colwidths, colaligns): - alignment = { "left": '', - "right": ' style="text-align: right;"', - "center": ' style="text-align: center;"', - "decimal": ' style="text-align: right;"' } - values_with_attrs = ["<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ''), c) - for c, a in zip(cell_values, colaligns)] - return "<tr>" + "".join(values_with_attrs).rstrip() + "</tr>" - - -def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False): - alignment = { "left": "l", "right": "r", "center": "c", "decimal": "r" } - tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns]) - return "\n".join(["\\begin{tabular}{" + tabular_columns_fmt + "}", - "\\toprule" if booktabs else "\hline"]) - -LATEX_ESCAPE_RULES = {r"&": r"\&", r"%": r"\%", r"$": r"\$", r"#": r"\#", - r"_": r"\_", r"^": r"\^{}", r"{": r"\{", r"}": r"\}", - r"~": r"\textasciitilde{}", "\\": r"\textbackslash{}", - r"<": r"\ensuremath{<}", r">": r"\ensuremath{>}"} - - -def _latex_row(cell_values, colwidths, colaligns): - def escape_char(c): - return LATEX_ESCAPE_RULES.get(c, c) - escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values] - rowfmt = DataRow("", "&", "\\\\") - return _build_simple_row(escaped_values, rowfmt) - - -_table_formats = {"simple": - TableFormat(lineabove=Line("", "-", " ", ""), - linebelowheader=Line("", "-", " ", ""), - linebetweenrows=None, - linebelow=Line("", "-", " ", ""), - headerrow=DataRow("", " ", ""), - datarow=DataRow("", " ", ""), - padding=0, - with_header_hide=["lineabove", "linebelow"]), - "plain": - TableFormat(lineabove=None, linebelowheader=None, - linebetweenrows=None, linebelow=None, - headerrow=DataRow("", " ", ""), - datarow=DataRow("", " ", ""), - padding=0, with_header_hide=None), - "grid": - TableFormat(lineabove=Line("+", "-", "+", "+"), - linebelowheader=Line("+", "=", "+", "+"), - linebetweenrows=Line("+", "-", "+", "+"), - linebelow=Line("+", "-", "+", "+"), - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, with_header_hide=None), - "fancy_grid": - TableFormat(lineabove=Line("╒", "═", "╤", "╕"), - linebelowheader=Line("╞", "═", "╪", "╡"), - linebetweenrows=Line("├", "─", "┼", "┤"), - linebelow=Line("╘", "═", "╧", "╛"), - headerrow=DataRow("│", "│", "│"), - datarow=DataRow("│", "│", "│"), - padding=1, with_header_hide=None), - "pipe": - TableFormat(lineabove=_pipe_line_with_colons, - linebelowheader=_pipe_line_with_colons, - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, - with_header_hide=["lineabove"]), - "orgtbl": - TableFormat(lineabove=None, - linebelowheader=Line("|", "-", "+", "|"), - linebetweenrows=None, - linebelow=None, - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, with_header_hide=None), - "psql": - TableFormat(lineabove=Line("+", "-", "+", "+"), - linebelowheader=Line("|", "-", "+", "|"), - linebetweenrows=None, - linebelow=Line("+", "-", "+", "+"), - headerrow=DataRow("|", "|", "|"), - datarow=DataRow("|", "|", "|"), - padding=1, with_header_hide=None), - "rst": - TableFormat(lineabove=Line("", "=", " ", ""), - linebelowheader=Line("", "=", " ", ""), - linebetweenrows=None, - linebelow=Line("", "=", " ", ""), - headerrow=DataRow("", " ", ""), - datarow=DataRow("", " ", ""), - padding=0, with_header_hide=None), - "mediawiki": - TableFormat(lineabove=Line("{| class=\"wikitable\" style=\"text-align: left;\"", - "", "", "\n|+ <!-- caption -->\n|-"), - linebelowheader=Line("|-", "", "", ""), - linebetweenrows=Line("|-", "", "", ""), - linebelow=Line("|}", "", "", ""), - headerrow=partial(_mediawiki_row_with_attrs, "!"), - datarow=partial(_mediawiki_row_with_attrs, "|"), - padding=0, with_header_hide=None), - "html": - TableFormat(lineabove=Line("<table>", "", "", ""), - linebelowheader=None, - linebetweenrows=None, - linebelow=Line("</table>", "", "", ""), - headerrow=partial(_html_row_with_attrs, "th"), - datarow=partial(_html_row_with_attrs, "td"), - padding=0, with_header_hide=None), - "latex": - TableFormat(lineabove=_latex_line_begin_tabular, - linebelowheader=Line("\\hline", "", "", ""), - linebetweenrows=None, - linebelow=Line("\\hline\n\\end{tabular}", "", "", ""), - headerrow=_latex_row, - datarow=_latex_row, - padding=1, with_header_hide=None), - "latex_booktabs": - TableFormat(lineabove=partial(_latex_line_begin_tabular, booktabs=True), - linebelowheader=Line("\\midrule", "", "", ""), - linebetweenrows=None, - linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""), - headerrow=_latex_row, - datarow=_latex_row, - padding=1, with_header_hide=None), - "tsv": - TableFormat(lineabove=None, linebelowheader=None, - linebetweenrows=None, linebelow=None, - headerrow=DataRow("", "\t", ""), - datarow=DataRow("", "\t", ""), - padding=0, with_header_hide=None)} - - -tabulate_formats = list(sorted(_table_formats.keys())) - - -_invisible_codes = re.compile(r"\x1b\[\d*m|\x1b\[\d*\;\d*\;\d*m") # ANSI color codes -_invisible_codes_bytes = re.compile(b"\x1b\[\d*m|\x1b\[\d*\;\d*\;\d*m") # ANSI color codes - - -def simple_separated_format(separator): - """Construct a simple TableFormat with columns separated by a separator. - - >>> tsv = simple_separated_format("\\t") ; \ - tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23' - True - - """ - return TableFormat(None, None, None, None, - headerrow=DataRow('', separator, ''), - datarow=DataRow('', separator, ''), - padding=0, with_header_hide=None) - - -def _isconvertible(conv, string): - try: - n = conv(string) - return True - except (ValueError, TypeError): - return False - - -def _isnumber(string): - """ - >>> _isnumber("123.45") - True - >>> _isnumber("123") - True - >>> _isnumber("spam") - False - """ - return _isconvertible(float, string) - - -def _isint(string, inttype=int): - """ - >>> _isint("123") - True - >>> _isint("123.45") - False - """ - return type(string) is inttype or\ - (isinstance(string, _binary_type) or isinstance(string, _text_type))\ - and\ - _isconvertible(inttype, string) - - -def _type(string, has_invisible=True): - """The least generic type (type(None), int, float, str, unicode). - - >>> _type(None) is type(None) - True - >>> _type("foo") is type("") - True - >>> _type("1") is type(1) - True - >>> _type('\x1b[31m42\x1b[0m') is type(42) - True - >>> _type('\x1b[31m42\x1b[0m') is type(42) - True - - """ - - if has_invisible and \ - (isinstance(string, _text_type) or isinstance(string, _binary_type)): - string = _strip_invisible(string) - - if string is None: - return _none_type - elif hasattr(string, "isoformat"): # datetime.datetime, date, and time - return _text_type - elif _isint(string): - return int - elif _isint(string, _long_type): - return _long_type - elif _isnumber(string): - return float - elif isinstance(string, _binary_type): - return _binary_type - else: - return _text_type - - -def _afterpoint(string): - """Symbols after a decimal point, -1 if the string lacks the decimal point. - - >>> _afterpoint("123.45") - 2 - >>> _afterpoint("1001") - -1 - >>> _afterpoint("eggs") - -1 - >>> _afterpoint("123e45") - 2 - - """ - if _isnumber(string): - if _isint(string): - return -1 - else: - pos = string.rfind(".") - pos = string.lower().rfind("e") if pos < 0 else pos - if pos >= 0: - return len(string) - pos - 1 - else: - return -1 # no point - else: - return -1 # not a number - - -def _padleft(width, s, has_invisible=True): - """Flush right. - - >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430' - True - - """ - iwidth = width + len(s) - len(_strip_invisible(s)) if has_invisible else width - fmt = "{0:>%ds}" % iwidth - return fmt.format(s) - - -def _padright(width, s, has_invisible=True): - """Flush left. - - >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 ' - True - - """ - iwidth = width + len(s) - len(_strip_invisible(s)) if has_invisible else width - fmt = "{0:<%ds}" % iwidth - return fmt.format(s) - - -def _padboth(width, s, has_invisible=True): - """Center string. - - >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 ' - True - - """ - iwidth = width + len(s) - len(_strip_invisible(s)) if has_invisible else width - fmt = "{0:^%ds}" % iwidth - return fmt.format(s) - - -def _strip_invisible(s): - "Remove invisible ANSI color codes." - if isinstance(s, _text_type): - return re.sub(_invisible_codes, "", s) - else: # a bytestring - return re.sub(_invisible_codes_bytes, "", s) - - -def _visible_width(s): - """Visible width of a printed string. ANSI color codes are removed. - - >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world") - (5, 5) - - """ - if isinstance(s, _text_type) or isinstance(s, _binary_type): - return len(_strip_invisible(s)) - else: - return len(_text_type(s)) - - -def _align_column(strings, alignment, minwidth=0, has_invisible=True): - """[string] -> [padded_string] - - >>> list(map(str,_align_column(["12.345", "-1234.5", "1.23", "1234.5", "1e+234", "1.0e234"], "decimal"))) - [' 12.345 ', '-1234.5 ', ' 1.23 ', ' 1234.5 ', ' 1e+234 ', ' 1.0e234'] - - >>> list(map(str,_align_column(['123.4', '56.7890'], None))) - ['123.4', '56.7890'] - - """ - if alignment == "right": - strings = [s.strip() for s in strings] - padfn = _padleft - elif alignment == "center": - strings = [s.strip() for s in strings] - padfn = _padboth - elif alignment == "decimal": - if has_invisible: - decimals = [_afterpoint(_strip_invisible(s)) for s in strings] - else: - decimals = [_afterpoint(s) for s in strings] - maxdecimals = max(decimals) - strings = [s + (maxdecimals - decs) * " " - for s, decs in zip(strings, decimals)] - padfn = _padleft - elif not alignment: - return strings - else: - strings = [s.strip() for s in strings] - padfn = _padright - - if has_invisible: - width_fn = _visible_width - else: - width_fn = len - - maxwidth = max(max(map(width_fn, strings)), minwidth) - padded_strings = [padfn(maxwidth, s, has_invisible) for s in strings] - return padded_strings - - -def _more_generic(type1, type2): - types = { _none_type: 0, int: 1, float: 2, _binary_type: 3, _text_type: 4 } - invtypes = { 4: _text_type, 3: _binary_type, 2: float, 1: int, 0: _none_type } - moregeneric = max(types.get(type1, 4), types.get(type2, 4)) - return invtypes[moregeneric] - - -def _column_type(strings, has_invisible=True): - """The least generic type all column values are convertible to. - - >>> _column_type(["1", "2"]) is _int_type - True - >>> _column_type(["1", "2.3"]) is _float_type - True - >>> _column_type(["1", "2.3", "four"]) is _text_type - True - >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is _text_type - True - >>> _column_type([None, "brux"]) is _text_type - True - >>> _column_type([1, 2, None]) is _int_type - True - >>> import datetime as dt - >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is _text_type - True - - """ - types = [_type(s, has_invisible) for s in strings ] - return reduce(_more_generic, types, int) - - -def _format(val, valtype, floatfmt, missingval="", has_invisible=True): - """Format a value accoding to its type. - - Unicode is supported: - - >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \ - tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \ - good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \ - tabulate(tbl, headers=hrow) == good_result - True - - """ - if val is None: - return missingval - - if valtype in [int, _long_type, _text_type]: - return "{0}".format(val) - elif valtype is _binary_type: - try: - return _text_type(val, "ascii") - except TypeError: - return _text_type(val) - elif valtype is float: - is_a_colored_number = has_invisible and isinstance(val, (_text_type, _binary_type)) - if is_a_colored_number: - raw_val = _strip_invisible(val) - formatted_val = format(float(raw_val), floatfmt) - return val.replace(raw_val, formatted_val) - else: - return format(float(val), floatfmt) - else: - return "{0}".format(val) - - -def _align_header(header, alignment, width): - if alignment == "left": - return _padright(width, header) - elif alignment == "center": - return _padboth(width, header) - elif not alignment: - return "{0}".format(header) - else: - return _padleft(width, header) - - -def _normalize_tabular_data(tabular_data, headers): - """Transform a supported data type to a list of lists, and a list of headers. - - Supported tabular data types: - - * list-of-lists or another iterable of iterables - - * list of named tuples (usually used with headers="keys") - - * list of dicts (usually used with headers="keys") - - * list of OrderedDicts (usually used with headers="keys") - - * 2D NumPy arrays - - * NumPy record arrays (usually used with headers="keys") - - * dict of iterables (usually used with headers="keys") - - * pandas.DataFrame (usually used with headers="keys") - - The first row can be used as headers if headers="firstrow", - column indices can be used as headers if headers="keys". - - """ - - if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"): - # dict-like and pandas.DataFrame? - if hasattr(tabular_data.values, "__call__"): - # likely a conventional dict - keys = tabular_data.keys() - rows = list(izip_longest(*tabular_data.values())) # columns have to be transposed - elif hasattr(tabular_data, "index"): - # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0) - keys = tabular_data.keys() - vals = tabular_data.values # values matrix doesn't need to be transposed - names = tabular_data.index - rows = [[v]+list(row) for v,row in zip(names, vals)] - else: - raise ValueError("tabular data doesn't appear to be a dict or a DataFrame") - - if headers == "keys": - headers = list(map(_text_type,keys)) # headers should be strings - - else: # it's a usual an iterable of iterables, or a NumPy array - rows = list(tabular_data) - - if (headers == "keys" and - hasattr(tabular_data, "dtype") and - getattr(tabular_data.dtype, "names")): - # numpy record array - headers = tabular_data.dtype.names - elif (headers == "keys" - and len(rows) > 0 - and isinstance(rows[0], tuple) - and hasattr(rows[0], "_fields")): - # namedtuple - headers = list(map(_text_type, rows[0]._fields)) - elif (len(rows) > 0 - and isinstance(rows[0], dict)): - # dict or OrderedDict - uniq_keys = set() # implements hashed lookup - keys = [] # storage for set - if headers == "firstrow": - firstdict = rows[0] if len(rows) > 0 else {} - keys.extend(firstdict.keys()) - uniq_keys.update(keys) - rows = rows[1:] - for row in rows: - for k in row.keys(): - #Save unique items in input order - if k not in uniq_keys: - keys.append(k) - uniq_keys.add(k) - if headers == 'keys': - headers = keys - elif isinstance(headers, dict): - # a dict of headers for a list of dicts - headers = [headers.get(k, k) for k in keys] - headers = list(map(_text_type, headers)) - elif headers == "firstrow": - if len(rows) > 0: - headers = [firstdict.get(k, k) for k in keys] - headers = list(map(_text_type, headers)) - else: - headers = [] - elif headers: - raise ValueError('headers for a list of dicts is not a dict or a keyword') - rows = [[row.get(k) for k in keys] for row in rows] - elif headers == "keys" and len(rows) > 0: - # keys are column indices - headers = list(map(_text_type, range(len(rows[0])))) - - # take headers from the first row if necessary - if headers == "firstrow" and len(rows) > 0: - headers = list(map(_text_type, rows[0])) # headers should be strings - rows = rows[1:] - - headers = list(map(_text_type,headers)) - rows = list(map(list,rows)) - - # pad with empty headers for initial columns if necessary - if headers and len(rows) > 0: - nhs = len(headers) - ncols = len(rows[0]) - if nhs < ncols: - headers = [""]*(ncols - nhs) + headers - - return rows, headers - - -def tabulate(tabular_data, headers=(), tablefmt="simple", - floatfmt="g", numalign="decimal", stralign="left", - missingval=""): - """Format a fixed width table for pretty printing. - - >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]])) - --- --------- - 1 2.34 - -56 8.999 - 2 10001 - --- --------- - - The first required argument (`tabular_data`) can be a - list-of-lists (or another iterable of iterables), a list of named - tuples, a dictionary of iterables, an iterable of dictionaries, - a two-dimensional NumPy array, NumPy record array, or a Pandas' - dataframe. - - - Table headers - ------------- - - To print nice column headers, supply the second argument (`headers`): - - - `headers` can be an explicit list of column headers - - if `headers="firstrow"`, then the first row of data is used - - if `headers="keys"`, then dictionary keys or column indices are used - - Otherwise a headerless table is produced. - - If the number of headers is less than the number of columns, they - are supposed to be names of the last columns. This is consistent - with the plain-text format of R and Pandas' dataframes. - - >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]], - ... headers="firstrow")) - sex age - ----- ----- ----- - Alice F 24 - Bob M 19 - - - Column alignment - ---------------- - - `tabulate` tries to detect column types automatically, and aligns - the values properly. By default it aligns decimal points of the - numbers (or flushes integer numbers to the right), and flushes - everything else to the left. Possible column alignments - (`numalign`, `stralign`) are: "right", "center", "left", "decimal" - (only for `numalign`), and None (to disable alignment). - - - Table formats - ------------- - - `floatfmt` is a format specification used for columns which - contain numeric data with a decimal point. - - `None` values are replaced with a `missingval` string: - - >>> print(tabulate([["spam", 1, None], - ... ["eggs", 42, 3.14], - ... ["other", None, 2.7]], missingval="?")) - ----- -- ---- - spam 1 ? - eggs 42 3.14 - other ? 2.7 - ----- -- ---- - - Various plain-text table formats (`tablefmt`) are supported: - 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki', - 'latex', and 'latex_booktabs'. Variable `tabulate_formats` contains the list of - currently supported formats. - - "plain" format doesn't use any pseudographics to draw tables, - it separates columns with a double space: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "plain")) - strings numbers - spam 41.9999 - eggs 451 - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain")) - spam 41.9999 - eggs 451 - - "simple" format is like Pandoc simple_tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "simple")) - strings numbers - --------- --------- - spam 41.9999 - eggs 451 - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple")) - ---- -------- - spam 41.9999 - eggs 451 - ---- -------- - - "grid" is similar to tables produced by Emacs table.el package or - Pandoc grid_tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "grid")) - +-----------+-----------+ - | strings | numbers | - +===========+===========+ - | spam | 41.9999 | - +-----------+-----------+ - | eggs | 451 | - +-----------+-----------+ - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid")) - +------+----------+ - | spam | 41.9999 | - +------+----------+ - | eggs | 451 | - +------+----------+ - - "fancy_grid" draws a grid using box-drawing characters: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "fancy_grid")) - ╒═══════════╤═══════════╕ - │ strings │ numbers │ - ╞═══════════╪═══════════╡ - │ spam │ 41.9999 │ - ├───────────┼───────────┤ - │ eggs │ 451 │ - ╘═══════════╧═══════════╛ - - "pipe" is like tables in PHP Markdown Extra extension or Pandoc - pipe_tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "pipe")) - | strings | numbers | - |:----------|----------:| - | spam | 41.9999 | - | eggs | 451 | - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe")) - |:-----|---------:| - | spam | 41.9999 | - | eggs | 451 | - - "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They - are slightly different from "pipe" format by not using colons to - define column alignment, and using a "+" sign to indicate line - intersections: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "orgtbl")) - | strings | numbers | - |-----------+-----------| - | spam | 41.9999 | - | eggs | 451 | - - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl")) - | spam | 41.9999 | - | eggs | 451 | - - "rst" is like a simple table format from reStructuredText; please - note that reStructuredText accepts also "grid" tables: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], - ... ["strings", "numbers"], "rst")) - ========= ========= - strings numbers - ========= ========= - spam 41.9999 - eggs 451 - ========= ========= - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst")) - ==== ======== - spam 41.9999 - eggs 451 - ==== ======== - - "mediawiki" produces a table markup used in Wikipedia and on other - MediaWiki-based sites: - - >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], - ... headers="firstrow", tablefmt="mediawiki")) - {| class="wikitable" style="text-align: left;" - |+ <!-- caption --> - |- - ! strings !! align="right"| numbers - |- - | spam || align="right"| 41.9999 - |- - | eggs || align="right"| 451 - |} - - "html" produces HTML markup: - - >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]], - ... headers="firstrow", tablefmt="html")) - <table> - <tr><th>strings </th><th style="text-align: right;"> numbers</th></tr> - <tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr> - <tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr> - </table> - - "latex" produces a tabular environment of LaTeX document markup: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex")) - \\begin{tabular}{lr} - \\hline - spam & 41.9999 \\\\ - eggs & 451 \\\\ - \\hline - \\end{tabular} - - "latex_booktabs" produces a tabular environment of LaTeX document markup - using the booktabs.sty package: - - >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs")) - \\begin{tabular}{lr} - \\toprule - spam & 41.9999 \\\\ - eggs & 451 \\\\ - \\bottomrule - \end{tabular} - """ - if tabular_data is None: - tabular_data = [] - list_of_lists, headers = _normalize_tabular_data(tabular_data, headers) - - # optimization: look for ANSI control codes once, - # enable smart width functions only if a control code is found - plain_text = '\n'.join(['\t'.join(map(_text_type, headers))] + \ - ['\t'.join(map(_text_type, row)) for row in list_of_lists]) - has_invisible = re.search(_invisible_codes, plain_text) - if has_invisible: - width_fn = _visible_width - else: - width_fn = len - - # format rows and columns, convert numeric values to strings - cols = list(zip(*list_of_lists)) - coltypes = list(map(_column_type, cols)) - cols = [[_format(v, ct, floatfmt, missingval, has_invisible) for v in c] - for c,ct in zip(cols, coltypes)] - - # align columns - aligns = [numalign if ct in [int,float] else stralign for ct in coltypes] - minwidths = [width_fn(h) + MIN_PADDING for h in headers] if headers else [0]*len(cols) - cols = [_align_column(c, a, minw, has_invisible) - for c, a, minw in zip(cols, aligns, minwidths)] - - if headers: - # align headers and add headers - t_cols = cols or [['']] * len(headers) - t_aligns = aligns or [stralign] * len(headers) - minwidths = [max(minw, width_fn(c[0])) for minw, c in zip(minwidths, t_cols)] - headers = [_align_header(h, a, minw) - for h, a, minw in zip(headers, t_aligns, minwidths)] - rows = list(zip(*cols)) - else: - minwidths = [width_fn(c[0]) for c in cols] - rows = list(zip(*cols)) - - if not isinstance(tablefmt, TableFormat): - tablefmt = _table_formats.get(tablefmt, _table_formats["simple"]) - - return _format_table(tablefmt, headers, rows, minwidths, aligns) - - -def _build_simple_row(padded_cells, rowfmt): - "Format row according to DataRow format without padding." - begin, sep, end = rowfmt - return (begin + sep.join(padded_cells) + end).rstrip() - - -def _build_row(padded_cells, colwidths, colaligns, rowfmt): - "Return a string which represents a row of data cells." - if not rowfmt: - return None - if hasattr(rowfmt, "__call__"): - return rowfmt(padded_cells, colwidths, colaligns) - else: - return _build_simple_row(padded_cells, rowfmt) - - -def _build_line(colwidths, colaligns, linefmt): - "Return a string which represents a horizontal line." - if not linefmt: - return None - if hasattr(linefmt, "__call__"): - return linefmt(colwidths, colaligns) - else: - begin, fill, sep, end = linefmt - cells = [fill*w for w in colwidths] - return _build_simple_row(cells, (begin, sep, end)) - - -def _pad_row(cells, padding): - if cells: - pad = " "*padding - padded_cells = [pad + cell + pad for cell in cells] - return padded_cells - else: - return cells - - -def _format_table(fmt, headers, rows, colwidths, colaligns): - """Produce a plain-text representation of the table.""" - lines = [] - hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else [] - pad = fmt.padding - headerrow = fmt.headerrow - - padded_widths = [(w + 2*pad) for w in colwidths] - padded_headers = _pad_row(headers, pad) - padded_rows = [_pad_row(row, pad) for row in rows] - - if fmt.lineabove and "lineabove" not in hidden: - lines.append(_build_line(padded_widths, colaligns, fmt.lineabove)) - - if padded_headers: - lines.append(_build_row(padded_headers, padded_widths, colaligns, headerrow)) - if fmt.linebelowheader and "linebelowheader" not in hidden: - lines.append(_build_line(padded_widths, colaligns, fmt.linebelowheader)) - - if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden: - # initial rows with a line below - for row in padded_rows[:-1]: - lines.append(_build_row(row, padded_widths, colaligns, fmt.datarow)) - lines.append(_build_line(padded_widths, colaligns, fmt.linebetweenrows)) - # the last row without a line below - lines.append(_build_row(padded_rows[-1], padded_widths, colaligns, fmt.datarow)) - else: - for row in padded_rows: - lines.append(_build_row(row, padded_widths, colaligns, fmt.datarow)) - - if fmt.linebelow and "linebelow" not in hidden: - lines.append(_build_line(padded_widths, colaligns, fmt.linebelow)) - - return "\n".join(lines) - - -def _main(): - """\ - Usage: tabulate [options] [FILE ...] - - Pretty-print tabular data. - See also https://bitbucket.org/astanin/python-tabulate - - FILE a filename of the file with tabular data; - if "-" or missing, read data from stdin. - - Options: - - -h, --help show this message - -1, --header use the first row of data as a table header - -o FILE, --output FILE print table to FILE (default: stdout) - -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace) - -F FPFMT, --float FPFMT floating point number format (default: g) - -f FMT, --format FMT set output table format; supported formats: - plain, simple, grid, fancy_grid, pipe, orgtbl, - rst, mediawiki, html, latex, latex_booktabs, tsv - (default: simple) - """ - import getopt - import sys - import textwrap - usage = textwrap.dedent(_main.__doc__) - try: - opts, args = getopt.getopt(sys.argv[1:], - "h1o:s:F:f:", - ["help", "header", "output", "sep=", "float=", "format="]) - except getopt.GetoptError as e: - print(e) - print(usage) - sys.exit(2) - headers = [] - floatfmt = "g" - tablefmt = "simple" - sep = r"\s+" - outfile = "-" - for opt, value in opts: - if opt in ["-1", "--header"]: - headers = "firstrow" - elif opt in ["-o", "--output"]: - outfile = value - elif opt in ["-F", "--float"]: - floatfmt = value - elif opt in ["-f", "--format"]: - if value not in tabulate_formats: - print("%s is not a supported table format" % value) - print(usage) - sys.exit(3) - tablefmt = value - elif opt in ["-s", "--sep"]: - sep = value - elif opt in ["-h", "--help"]: - print(usage) - sys.exit(0) - files = [sys.stdin] if not args else args - with (sys.stdout if outfile == "-" else open(outfile, "w")) as out: - for f in files: - if f == "-": - f = sys.stdin - if _is_file(f): - _pprint_file(f, headers=headers, tablefmt=tablefmt, - sep=sep, floatfmt=floatfmt, file=out) - else: - with open(f) as fobj: - _pprint_file(fobj, headers=headers, tablefmt=tablefmt, - sep=sep, floatfmt=floatfmt, file=out) - - -def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, file): - rows = fobject.readlines() - table = [re.split(sep, r.rstrip()) for r in rows] - print(tabulate(table, headers, tablefmt, floatfmt=floatfmt), file=file) - - -if __name__ == "__main__": - _main() +# -*- coding: utf-8 -*-
+
+"""Pretty-print tabular data."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from collections import namedtuple, Iterable
+from platform import python_version_tuple
+import re
+
+
+if python_version_tuple()[0] < "3":
+ from itertools import izip_longest
+ from functools import partial
+ _none_type = type(None)
+ _bool_type = bool
+ _int_type = int
+ _long_type = long
+ _float_type = float
+ _text_type = unicode
+ _binary_type = str
+
+ def _is_file(f):
+ return isinstance(f, file)
+
+else:
+ from itertools import zip_longest as izip_longest
+ from functools import reduce, partial
+ _none_type = type(None)
+ _bool_type = bool
+ _int_type = int
+ _long_type = int
+ _float_type = float
+ _text_type = str
+ _binary_type = bytes
+
+ import io
+ def _is_file(f):
+ return isinstance(f, io.IOBase)
+
+try:
+ import wcwidth # optional wide-character (CJK) support
+except ImportError:
+ wcwidth = None
+
+
+__all__ = ["tabulate", "tabulate_formats", "simple_separated_format"]
+__version__ = "0.7.7"
+
+
+# minimum extra space in headers
+MIN_PADDING = 2
+
+# if True, enable wide-character (CJK) support
+WIDE_CHARS_MODE = wcwidth is not None
+
+
+Line = namedtuple("Line", ["begin", "hline", "sep", "end"])
+
+
+DataRow = namedtuple("DataRow", ["begin", "sep", "end"])
+
+
+# A table structure is suppposed to be:
+#
+# --- lineabove ---------
+# headerrow
+# --- linebelowheader ---
+# datarow
+# --- linebewteenrows ---
+# ... (more datarows) ...
+# --- linebewteenrows ---
+# last datarow
+# --- linebelow ---------
+#
+# TableFormat's line* elements can be
+#
+# - either None, if the element is not used,
+# - or a Line tuple,
+# - or a function: [col_widths], [col_alignments] -> string.
+#
+# TableFormat's *row elements can be
+#
+# - either None, if the element is not used,
+# - or a DataRow tuple,
+# - or a function: [cell_values], [col_widths], [col_alignments] -> string.
+#
+# padding (an integer) is the amount of white space around data values.
+#
+# with_header_hide:
+#
+# - either None, to display all table elements unconditionally,
+# - or a list of elements not to be displayed if the table has column headers.
+#
+TableFormat = namedtuple("TableFormat", ["lineabove", "linebelowheader",
+ "linebetweenrows", "linebelow",
+ "headerrow", "datarow",
+ "padding", "with_header_hide"])
+
+
+def _pipe_segment_with_colons(align, colwidth):
+ """Return a segment of a horizontal line with optional colons which
+ indicate column's alignment (as in `pipe` output format)."""
+ w = colwidth
+ if align in ["right", "decimal"]:
+ return ('-' * (w - 1)) + ":"
+ elif align == "center":
+ return ":" + ('-' * (w - 2)) + ":"
+ elif align == "left":
+ return ":" + ('-' * (w - 1))
+ else:
+ return '-' * w
+
+
+def _pipe_line_with_colons(colwidths, colaligns):
+ """Return a horizontal line with optional colons to indicate column's
+ alignment (as in `pipe` output format)."""
+ segments = [_pipe_segment_with_colons(a, w) for a, w in zip(colaligns, colwidths)]
+ return "|" + "|".join(segments) + "|"
+
+
+def _mediawiki_row_with_attrs(separator, cell_values, colwidths, colaligns):
+ alignment = { "left": '',
+ "right": 'align="right"| ',
+ "center": 'align="center"| ',
+ "decimal": 'align="right"| ' }
+ # hard-coded padding _around_ align attribute and value together
+ # rather than padding parameter which affects only the value
+ values_with_attrs = [' ' + alignment.get(a, '') + c + ' '
+ for c, a in zip(cell_values, colaligns)]
+ colsep = separator*2
+ return (separator + colsep.join(values_with_attrs)).rstrip()
+
+
+def _textile_row_with_attrs(cell_values, colwidths, colaligns):
+ cell_values[0] += ' '
+ alignment = { "left": "<.", "right": ">.", "center": "=.", "decimal": ">." }
+ values = (alignment.get(a, '') + v for a, v in zip(colaligns, cell_values))
+ return '|' + '|'.join(values) + '|'
+
+
+def _html_begin_table_without_header(colwidths_ignore, colaligns_ignore):
+ # this table header will be suppressed if there is a header row
+ return "\n".join(["<table>", "<tbody>"])
+
+
+def _html_row_with_attrs(celltag, cell_values, colwidths, colaligns):
+ alignment = { "left": '',
+ "right": ' style="text-align: right;"',
+ "center": ' style="text-align: center;"',
+ "decimal": ' style="text-align: right;"' }
+ values_with_attrs = ["<{0}{1}>{2}</{0}>".format(celltag, alignment.get(a, ''), c)
+ for c, a in zip(cell_values, colaligns)]
+ rowhtml = "<tr>" + "".join(values_with_attrs).rstrip() + "</tr>"
+ if celltag == "th": # it's a header row, create a new table header
+ rowhtml = "\n".join(["<table>",
+ "<thead>",
+ rowhtml,
+ "</thead>",
+ "<tbody>"])
+ return rowhtml
+
+def _moin_row_with_attrs(celltag, cell_values, colwidths, colaligns, header=''):
+ alignment = { "left": '',
+ "right": '<style="text-align: right;">',
+ "center": '<style="text-align: center;">',
+ "decimal": '<style="text-align: right;">' }
+ values_with_attrs = ["{0}{1} {2} ".format(celltag,
+ alignment.get(a, ''),
+ header+c+header)
+ for c, a in zip(cell_values, colaligns)]
+ return "".join(values_with_attrs)+"||"
+
+def _latex_line_begin_tabular(colwidths, colaligns, booktabs=False):
+ alignment = { "left": "l", "right": "r", "center": "c", "decimal": "r" }
+ tabular_columns_fmt = "".join([alignment.get(a, "l") for a in colaligns])
+ return "\n".join(["\\begin{tabular}{" + tabular_columns_fmt + "}",
+ "\\toprule" if booktabs else "\hline"])
+
+LATEX_ESCAPE_RULES = {r"&": r"\&", r"%": r"\%", r"$": r"\$", r"#": r"\#",
+ r"_": r"\_", r"^": r"\^{}", r"{": r"\{", r"}": r"\}",
+ r"~": r"\textasciitilde{}", "\\": r"\textbackslash{}",
+ r"<": r"\ensuremath{<}", r">": r"\ensuremath{>}"}
+
+
+def _latex_row(cell_values, colwidths, colaligns):
+ def escape_char(c):
+ return LATEX_ESCAPE_RULES.get(c, c)
+ escaped_values = ["".join(map(escape_char, cell)) for cell in cell_values]
+ rowfmt = DataRow("", "&", "\\\\")
+ return _build_simple_row(escaped_values, rowfmt)
+
+
+def _rst_escape_first_column(rows, headers):
+ def escape_empty(val):
+ if isinstance(val, (_text_type, _binary_type)) and val.strip() is "":
+ return ".."
+ else:
+ return val
+ new_headers = list(headers)
+ new_rows = []
+ if headers:
+ new_headers[0] = escape_empty(headers[0])
+ for row in rows:
+ new_row = list(row)
+ if new_row:
+ new_row[0] = escape_empty(row[0])
+ new_rows.append(new_row)
+ return new_rows, new_headers
+
+
+_table_formats = {"simple":
+ TableFormat(lineabove=Line("", "-", " ", ""),
+ linebelowheader=Line("", "-", " ", ""),
+ linebetweenrows=None,
+ linebelow=Line("", "-", " ", ""),
+ headerrow=DataRow("", " ", ""),
+ datarow=DataRow("", " ", ""),
+ padding=0,
+ with_header_hide=["lineabove", "linebelow"]),
+ "plain":
+ TableFormat(lineabove=None, linebelowheader=None,
+ linebetweenrows=None, linebelow=None,
+ headerrow=DataRow("", " ", ""),
+ datarow=DataRow("", " ", ""),
+ padding=0, with_header_hide=None),
+ "grid":
+ TableFormat(lineabove=Line("+", "-", "+", "+"),
+ linebelowheader=Line("+", "=", "+", "+"),
+ linebetweenrows=Line("+", "-", "+", "+"),
+ linebelow=Line("+", "-", "+", "+"),
+ headerrow=DataRow("|", "|", "|"),
+ datarow=DataRow("|", "|", "|"),
+ padding=1, with_header_hide=None),
+ "fancy_grid":
+ TableFormat(lineabove=Line("╒", "═", "╤", "╕"),
+ linebelowheader=Line("╞", "═", "╪", "╡"),
+ linebetweenrows=Line("├", "─", "┼", "┤"),
+ linebelow=Line("╘", "═", "╧", "╛"),
+ headerrow=DataRow("│", "│", "│"),
+ datarow=DataRow("│", "│", "│"),
+ padding=1, with_header_hide=None),
+ "pipe":
+ TableFormat(lineabove=_pipe_line_with_colons,
+ linebelowheader=_pipe_line_with_colons,
+ linebetweenrows=None,
+ linebelow=None,
+ headerrow=DataRow("|", "|", "|"),
+ datarow=DataRow("|", "|", "|"),
+ padding=1,
+ with_header_hide=["lineabove"]),
+ "orgtbl":
+ TableFormat(lineabove=None,
+ linebelowheader=Line("|", "-", "+", "|"),
+ linebetweenrows=None,
+ linebelow=None,
+ headerrow=DataRow("|", "|", "|"),
+ datarow=DataRow("|", "|", "|"),
+ padding=1, with_header_hide=None),
+ "jira":
+ TableFormat(lineabove=None,
+ linebelowheader=None,
+ linebetweenrows=None,
+ linebelow=None,
+ headerrow=DataRow("||", "||", "||"),
+ datarow=DataRow("|", "|", "|"),
+ padding=1, with_header_hide=None),
+ "psql":
+ TableFormat(lineabove=Line("+", "-", "+", "+"),
+ linebelowheader=Line("|", "-", "+", "|"),
+ linebetweenrows=None,
+ linebelow=Line("+", "-", "+", "+"),
+ headerrow=DataRow("|", "|", "|"),
+ datarow=DataRow("|", "|", "|"),
+ padding=1, with_header_hide=None),
+ "rst":
+ TableFormat(lineabove=Line("", "=", " ", ""),
+ linebelowheader=Line("", "=", " ", ""),
+ linebetweenrows=None,
+ linebelow=Line("", "=", " ", ""),
+ headerrow=DataRow("", " ", ""),
+ datarow=DataRow("", " ", ""),
+ padding=0, with_header_hide=None),
+ "mediawiki":
+ TableFormat(lineabove=Line("{| class=\"wikitable\" style=\"text-align: left;\"",
+ "", "", "\n|+ <!-- caption -->\n|-"),
+ linebelowheader=Line("|-", "", "", ""),
+ linebetweenrows=Line("|-", "", "", ""),
+ linebelow=Line("|}", "", "", ""),
+ headerrow=partial(_mediawiki_row_with_attrs, "!"),
+ datarow=partial(_mediawiki_row_with_attrs, "|"),
+ padding=0, with_header_hide=None),
+ "moinmoin":
+ TableFormat(lineabove=None,
+ linebelowheader=None,
+ linebetweenrows=None,
+ linebelow=None,
+ headerrow=partial(_moin_row_with_attrs,"||",header="'''"),
+ datarow=partial(_moin_row_with_attrs,"||"),
+ padding=1, with_header_hide=None),
+ "html":
+ TableFormat(lineabove=_html_begin_table_without_header,
+ linebelowheader="",
+ linebetweenrows=None,
+ linebelow=Line("</tbody>\n</table>", "", "", ""),
+ headerrow=partial(_html_row_with_attrs, "th"),
+ datarow=partial(_html_row_with_attrs, "td"),
+ padding=0, with_header_hide=["lineabove"]),
+ "latex":
+ TableFormat(lineabove=_latex_line_begin_tabular,
+ linebelowheader=Line("\\hline", "", "", ""),
+ linebetweenrows=None,
+ linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
+ headerrow=_latex_row,
+ datarow=_latex_row,
+ padding=1, with_header_hide=None),
+ "latex_booktabs":
+ TableFormat(lineabove=partial(_latex_line_begin_tabular, booktabs=True),
+ linebelowheader=Line("\\midrule", "", "", ""),
+ linebetweenrows=None,
+ linebelow=Line("\\bottomrule\n\\end{tabular}", "", "", ""),
+ headerrow=_latex_row,
+ datarow=_latex_row,
+ padding=1, with_header_hide=None),
+ "tsv":
+ TableFormat(lineabove=None, linebelowheader=None,
+ linebetweenrows=None, linebelow=None,
+ headerrow=DataRow("", "\t", ""),
+ datarow=DataRow("", "\t", ""),
+ padding=0, with_header_hide=None),
+ "textile":
+ TableFormat(lineabove=None, linebelowheader=None,
+ linebetweenrows=None, linebelow=None,
+ headerrow=DataRow("|_. ", "|_.", "|"),
+ datarow=_textile_row_with_attrs,
+ padding=1, with_header_hide=None)}
+
+
+tabulate_formats = list(sorted(_table_formats.keys()))
+
+
+_invisible_codes = re.compile(r"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m") # ANSI color codes
+_invisible_codes_bytes = re.compile(b"\x1b\[\d+[;\d]*m|\x1b\[\d*\;\d*\;\d*m") # ANSI color codes
+
+
+def simple_separated_format(separator):
+ """Construct a simple TableFormat with columns separated by a separator.
+
+ >>> tsv = simple_separated_format("\\t") ; \
+ tabulate([["foo", 1], ["spam", 23]], tablefmt=tsv) == 'foo \\t 1\\nspam\\t23'
+ True
+
+ """
+ return TableFormat(None, None, None, None,
+ headerrow=DataRow('', separator, ''),
+ datarow=DataRow('', separator, ''),
+ padding=0, with_header_hide=None)
+
+
+def _isconvertible(conv, string):
+ try:
+ n = conv(string)
+ return True
+ except (ValueError, TypeError):
+ return False
+
+
+def _isnumber(string):
+ """
+ >>> _isnumber("123.45")
+ True
+ >>> _isnumber("123")
+ True
+ >>> _isnumber("spam")
+ False
+ """
+ return _isconvertible(float, string)
+
+
+def _isint(string, inttype=int):
+ """
+ >>> _isint("123")
+ True
+ >>> _isint("123.45")
+ False
+ """
+ return type(string) is inttype or\
+ (isinstance(string, _binary_type) or isinstance(string, _text_type))\
+ and\
+ _isconvertible(inttype, string)
+
+
+def _isbool(string):
+ """
+ >>> _isbool(True)
+ True
+ >>> _isbool("False")
+ True
+ >>> _isbool(1)
+ False
+ """
+ return type(string) is _bool_type or\
+ (isinstance(string, (_binary_type, _text_type))\
+ and\
+ string in ("True", "False"))
+
+
+def _type(string, has_invisible=True, numparse=True):
+ """The least generic type (type(None), int, float, str, unicode).
+
+ >>> _type(None) is type(None)
+ True
+ >>> _type("foo") is type("")
+ True
+ >>> _type("1") is type(1)
+ True
+ >>> _type('\x1b[31m42\x1b[0m') is type(42)
+ True
+ >>> _type('\x1b[31m42\x1b[0m') is type(42)
+ True
+
+ """
+
+ if has_invisible and \
+ (isinstance(string, _text_type) or isinstance(string, _binary_type)):
+ string = _strip_invisible(string)
+
+ if string is None:
+ return _none_type
+ elif hasattr(string, "isoformat"): # datetime.datetime, date, and time
+ return _text_type
+ elif _isbool(string):
+ return _bool_type
+ elif _isint(string) and numparse:
+ return int
+ elif _isint(string, _long_type) and numparse:
+ return int
+ elif _isnumber(string) and numparse:
+ return float
+ elif isinstance(string, _binary_type):
+ return _binary_type
+ else:
+ return _text_type
+
+
+def _afterpoint(string):
+ """Symbols after a decimal point, -1 if the string lacks the decimal point.
+
+ >>> _afterpoint("123.45")
+ 2
+ >>> _afterpoint("1001")
+ -1
+ >>> _afterpoint("eggs")
+ -1
+ >>> _afterpoint("123e45")
+ 2
+
+ """
+ if _isnumber(string):
+ if _isint(string):
+ return -1
+ else:
+ pos = string.rfind(".")
+ pos = string.lower().rfind("e") if pos < 0 else pos
+ if pos >= 0:
+ return len(string) - pos - 1
+ else:
+ return -1 # no point
+ else:
+ return -1 # not a number
+
+
+def _padleft(width, s):
+ """Flush right.
+
+ >>> _padleft(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430'
+ True
+
+ """
+ fmt = "{0:>%ds}" % width
+ return fmt.format(s)
+
+
+def _padright(width, s):
+ """Flush left.
+
+ >>> _padright(6, '\u044f\u0439\u0446\u0430') == '\u044f\u0439\u0446\u0430 '
+ True
+
+ """
+ fmt = "{0:<%ds}" % width
+ return fmt.format(s)
+
+
+def _padboth(width, s):
+ """Center string.
+
+ >>> _padboth(6, '\u044f\u0439\u0446\u0430') == ' \u044f\u0439\u0446\u0430 '
+ True
+
+ """
+ fmt = "{0:^%ds}" % width
+ return fmt.format(s)
+
+
+def _strip_invisible(s):
+ "Remove invisible ANSI color codes."
+ if isinstance(s, _text_type):
+ return re.sub(_invisible_codes, "", s)
+ else: # a bytestring
+ return re.sub(_invisible_codes_bytes, "", s)
+
+
+def _visible_width(s):
+ """Visible width of a printed string. ANSI color codes are removed.
+
+ >>> _visible_width('\x1b[31mhello\x1b[0m'), _visible_width("world")
+ (5, 5)
+
+ """
+ # optional wide-character support
+ if wcwidth is not None and WIDE_CHARS_MODE:
+ len_fn = wcwidth.wcswidth
+ else:
+ len_fn = len
+ if isinstance(s, _text_type) or isinstance(s, _binary_type):
+ return len_fn(_strip_invisible(s))
+ else:
+ return len_fn(_text_type(s))
+
+
+def _align_column(strings, alignment, minwidth=0, has_invisible=True):
+ """[string] -> [padded_string]
+
+ >>> list(map(str,_align_column(["12.345", "-1234.5", "1.23", "1234.5", "1e+234", "1.0e234"], "decimal")))
+ [' 12.345 ', '-1234.5 ', ' 1.23 ', ' 1234.5 ', ' 1e+234 ', ' 1.0e234']
+
+ >>> list(map(str,_align_column(['123.4', '56.7890'], None)))
+ ['123.4', '56.7890']
+
+ """
+ if alignment == "right":
+ strings = [s.strip() for s in strings]
+ padfn = _padleft
+ elif alignment == "center":
+ strings = [s.strip() for s in strings]
+ padfn = _padboth
+ elif alignment == "decimal":
+ if has_invisible:
+ decimals = [_afterpoint(_strip_invisible(s)) for s in strings]
+ else:
+ decimals = [_afterpoint(s) for s in strings]
+ maxdecimals = max(decimals)
+ strings = [s + (maxdecimals - decs) * " "
+ for s, decs in zip(strings, decimals)]
+ padfn = _padleft
+ elif not alignment:
+ return strings
+ else:
+ strings = [s.strip() for s in strings]
+ padfn = _padright
+
+ enable_widechars = wcwidth is not None and WIDE_CHARS_MODE
+ if has_invisible:
+ width_fn = _visible_width
+ elif enable_widechars: # optional wide-character support if available
+ width_fn = wcwidth.wcswidth
+ else:
+ width_fn = len
+
+ s_lens = list(map(len, strings))
+ s_widths = list(map(width_fn, strings))
+ maxwidth = max(max(s_widths), minwidth)
+ if not enable_widechars and not has_invisible:
+ padded_strings = [padfn(maxwidth, s) for s in strings]
+ else:
+ # enable wide-character width corrections
+ visible_widths = [maxwidth - (w - l) for w, l in zip(s_widths, s_lens)]
+ # wcswidth and _visible_width don't count invisible characters;
+ # padfn doesn't need to apply another correction
+ padded_strings = [padfn(w, s) for s, w in zip(strings, visible_widths)]
+ return padded_strings
+
+
+def _more_generic(type1, type2):
+ types = { _none_type: 0, _bool_type: 1, int: 2, float: 3, _binary_type: 4, _text_type: 5 }
+ invtypes = { 5: _text_type, 4: _binary_type, 3: float, 2: int, 1: _bool_type, 0: _none_type }
+ moregeneric = max(types.get(type1, 5), types.get(type2, 5))
+ return invtypes[moregeneric]
+
+
+def _column_type(strings, has_invisible=True, numparse=True):
+ """The least generic type all column values are convertible to.
+
+ >>> _column_type([True, False]) is _bool_type
+ True
+ >>> _column_type(["1", "2"]) is _int_type
+ True
+ >>> _column_type(["1", "2.3"]) is _float_type
+ True
+ >>> _column_type(["1", "2.3", "four"]) is _text_type
+ True
+ >>> _column_type(["four", '\u043f\u044f\u0442\u044c']) is _text_type
+ True
+ >>> _column_type([None, "brux"]) is _text_type
+ True
+ >>> _column_type([1, 2, None]) is _int_type
+ True
+ >>> import datetime as dt
+ >>> _column_type([dt.datetime(1991,2,19), dt.time(17,35)]) is _text_type
+ True
+
+ """
+ types = [_type(s, has_invisible, numparse) for s in strings ]
+ return reduce(_more_generic, types, _bool_type)
+
+
+def _format(val, valtype, floatfmt, missingval="", has_invisible=True):
+ """Format a value accoding to its type.
+
+ Unicode is supported:
+
+ >>> hrow = ['\u0431\u0443\u043a\u0432\u0430', '\u0446\u0438\u0444\u0440\u0430'] ; \
+ tbl = [['\u0430\u0437', 2], ['\u0431\u0443\u043a\u0438', 4]] ; \
+ good_result = '\\u0431\\u0443\\u043a\\u0432\\u0430 \\u0446\\u0438\\u0444\\u0440\\u0430\\n------- -------\\n\\u0430\\u0437 2\\n\\u0431\\u0443\\u043a\\u0438 4' ; \
+ tabulate(tbl, headers=hrow) == good_result
+ True
+
+ """
+ if val is None:
+ return missingval
+
+ if valtype in [int, _text_type]:
+ return "{0}".format(val)
+ elif valtype is _binary_type:
+ try:
+ return _text_type(val, "ascii")
+ except TypeError:
+ return _text_type(val)
+ elif valtype is float:
+ is_a_colored_number = has_invisible and isinstance(val, (_text_type, _binary_type))
+ if is_a_colored_number:
+ raw_val = _strip_invisible(val)
+ formatted_val = format(float(raw_val), floatfmt)
+ return val.replace(raw_val, formatted_val)
+ else:
+ return format(float(val), floatfmt)
+ else:
+ return "{0}".format(val)
+
+
+def _align_header(header, alignment, width, visible_width):
+ "Pad string header to width chars given known visible_width of the header."
+ width += len(header) - visible_width
+ if alignment == "left":
+ return _padright(width, header)
+ elif alignment == "center":
+ return _padboth(width, header)
+ elif not alignment:
+ return "{0}".format(header)
+ else:
+ return _padleft(width, header)
+
+
+def _prepend_row_index(rows, index):
+ """Add a left-most index column."""
+ if index is None or index is False:
+ return rows
+ if len(index) != len(rows):
+ print('index=', index)
+ print('rows=', rows)
+ raise ValueError('index must be as long as the number of data rows')
+ rows = [[v]+list(row) for v,row in zip(index, rows)]
+ return rows
+
+
+def _bool(val):
+ "A wrapper around standard bool() which doesn't throw on NumPy arrays"
+ try:
+ return bool(val)
+ except ValueError: # val is likely to be a numpy array with many elements
+ return False
+
+
+def _normalize_tabular_data(tabular_data, headers, showindex="default"):
+ """Transform a supported data type to a list of lists, and a list of headers.
+
+ Supported tabular data types:
+
+ * list-of-lists or another iterable of iterables
+
+ * list of named tuples (usually used with headers="keys")
+
+ * list of dicts (usually used with headers="keys")
+
+ * list of OrderedDicts (usually used with headers="keys")
+
+ * 2D NumPy arrays
+
+ * NumPy record arrays (usually used with headers="keys")
+
+ * dict of iterables (usually used with headers="keys")
+
+ * pandas.DataFrame (usually used with headers="keys")
+
+ The first row can be used as headers if headers="firstrow",
+ column indices can be used as headers if headers="keys".
+
+ If showindex="default", show row indices of the pandas.DataFrame.
+ If showindex="always", show row indices for all types of data.
+ If showindex="never", don't show row indices for all types of data.
+ If showindex is an iterable, show its values as row indices.
+
+ """
+
+ try:
+ bool(headers)
+ is_headers2bool_broken = False
+ except ValueError: # numpy.ndarray, pandas.core.index.Index, ...
+ is_headers2bool_broken = True
+ headers = list(headers)
+
+ index = None
+ if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
+ # dict-like and pandas.DataFrame?
+ if hasattr(tabular_data.values, "__call__"):
+ # likely a conventional dict
+ keys = tabular_data.keys()
+ rows = list(izip_longest(*tabular_data.values())) # columns have to be transposed
+ elif hasattr(tabular_data, "index"):
+ # values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
+ keys = list(tabular_data)
+ if tabular_data.index.name is not None:
+ if isinstance(tabular_data.index.name, list):
+ keys[:0] = tabular_data.index.name
+ else:
+ keys[:0] = [tabular_data.index.name]
+ vals = tabular_data.values # values matrix doesn't need to be transposed
+ # for DataFrames add an index per default
+ index = list(tabular_data.index)
+ rows = [list(row) for row in vals]
+ else:
+ raise ValueError("tabular data doesn't appear to be a dict or a DataFrame")
+
+ if headers == "keys":
+ headers = list(map(_text_type,keys)) # headers should be strings
+
+ else: # it's a usual an iterable of iterables, or a NumPy array
+ rows = list(tabular_data)
+
+ if (headers == "keys" and not rows):
+ # an empty table (issue #81)
+ headers = []
+ elif (headers == "keys" and
+ hasattr(tabular_data, "dtype") and
+ getattr(tabular_data.dtype, "names")):
+ # numpy record array
+ headers = tabular_data.dtype.names
+ elif (headers == "keys"
+ and len(rows) > 0
+ and isinstance(rows[0], tuple)
+ and hasattr(rows[0], "_fields")):
+ # namedtuple
+ headers = list(map(_text_type, rows[0]._fields))
+ elif (len(rows) > 0
+ and isinstance(rows[0], dict)):
+ # dict or OrderedDict
+ uniq_keys = set() # implements hashed lookup
+ keys = [] # storage for set
+ if headers == "firstrow":
+ firstdict = rows[0] if len(rows) > 0 else {}
+ keys.extend(firstdict.keys())
+ uniq_keys.update(keys)
+ rows = rows[1:]
+ for row in rows:
+ for k in row.keys():
+ #Save unique items in input order
+ if k not in uniq_keys:
+ keys.append(k)
+ uniq_keys.add(k)
+ if headers == 'keys':
+ headers = keys
+ elif isinstance(headers, dict):
+ # a dict of headers for a list of dicts
+ headers = [headers.get(k, k) for k in keys]
+ headers = list(map(_text_type, headers))
+ elif headers == "firstrow":
+ if len(rows) > 0:
+ headers = [firstdict.get(k, k) for k in keys]
+ headers = list(map(_text_type, headers))
+ else:
+ headers = []
+ elif headers:
+ raise ValueError('headers for a list of dicts is not a dict or a keyword')
+ rows = [[row.get(k) for k in keys] for row in rows]
+
+ elif (headers == "keys"
+ and hasattr(tabular_data, "description")
+ and hasattr(tabular_data, "fetchone")
+ and hasattr(tabular_data, "rowcount")):
+ # Python Database API cursor object (PEP 0249)
+ # print tabulate(cursor, headers='keys')
+ headers = [column[0] for column in tabular_data.description]
+
+ elif headers == "keys" and len(rows) > 0:
+ # keys are column indices
+ headers = list(map(_text_type, range(len(rows[0]))))
+
+ # take headers from the first row if necessary
+ if headers == "firstrow" and len(rows) > 0:
+ if index is not None:
+ headers = [index[0]] + list(rows[0])
+ index = index[1:]
+ else:
+ headers = rows[0]
+ headers = list(map(_text_type, headers)) # headers should be strings
+ rows = rows[1:]
+
+ headers = list(map(_text_type,headers))
+ rows = list(map(list,rows))
+
+ # add or remove an index column
+ showindex_is_a_str = type(showindex) in [_text_type, _binary_type]
+ if showindex == "default" and index is not None:
+ rows = _prepend_row_index(rows, index)
+ elif isinstance(showindex, Iterable) and not showindex_is_a_str:
+ rows = _prepend_row_index(rows, list(showindex))
+ elif showindex == "always" or (_bool(showindex) and not showindex_is_a_str):
+ if index is None:
+ index = list(range(len(rows)))
+ rows = _prepend_row_index(rows, index)
+ elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str):
+ pass
+
+ # pad with empty headers for initial columns if necessary
+ if headers and len(rows) > 0:
+ nhs = len(headers)
+ ncols = len(rows[0])
+ if nhs < ncols:
+ headers = [""]*(ncols - nhs) + headers
+
+ return rows, headers
+
+
+def tabulate(tabular_data, headers=(), tablefmt="simple",
+ floatfmt="g", numalign="decimal", stralign="left",
+ missingval="", showindex="default", disable_numparse=False):
+ """Format a fixed width table for pretty printing.
+
+ >>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]]))
+ --- ---------
+ 1 2.34
+ -56 8.999
+ 2 10001
+ --- ---------
+
+ The first required argument (`tabular_data`) can be a
+ list-of-lists (or another iterable of iterables), a list of named
+ tuples, a dictionary of iterables, an iterable of dictionaries,
+ a two-dimensional NumPy array, NumPy record array, or a Pandas'
+ dataframe.
+
+
+ Table headers
+ -------------
+
+ To print nice column headers, supply the second argument (`headers`):
+
+ - `headers` can be an explicit list of column headers
+ - if `headers="firstrow"`, then the first row of data is used
+ - if `headers="keys"`, then dictionary keys or column indices are used
+
+ Otherwise a headerless table is produced.
+
+ If the number of headers is less than the number of columns, they
+ are supposed to be names of the last columns. This is consistent
+ with the plain-text format of R and Pandas' dataframes.
+
+ >>> print(tabulate([["sex","age"],["Alice","F",24],["Bob","M",19]],
+ ... headers="firstrow"))
+ sex age
+ ----- ----- -----
+ Alice F 24
+ Bob M 19
+
+ By default, pandas.DataFrame data have an additional column called
+ row index. To add a similar column to all other types of data,
+ use `showindex="always"` or `showindex=True`. To suppress row indices
+ for all types of data, pass `showindex="never" or `showindex=False`.
+ To add a custom row index column, pass `showindex=some_iterable`.
+
+ >>> print(tabulate([["F",24],["M",19]], showindex="always"))
+ - - --
+ 0 F 24
+ 1 M 19
+ - - --
+
+
+ Column alignment
+ ----------------
+
+ `tabulate` tries to detect column types automatically, and aligns
+ the values properly. By default it aligns decimal points of the
+ numbers (or flushes integer numbers to the right), and flushes
+ everything else to the left. Possible column alignments
+ (`numalign`, `stralign`) are: "right", "center", "left", "decimal"
+ (only for `numalign`), and None (to disable alignment).
+
+
+ Table formats
+ -------------
+
+ `floatfmt` is a format specification used for columns which
+ contain numeric data with a decimal point.
+
+ `None` values are replaced with a `missingval` string:
+
+ >>> print(tabulate([["spam", 1, None],
+ ... ["eggs", 42, 3.14],
+ ... ["other", None, 2.7]], missingval="?"))
+ ----- -- ----
+ spam 1 ?
+ eggs 42 3.14
+ other ? 2.7
+ ----- -- ----
+
+ Various plain-text table formats (`tablefmt`) are supported:
+ 'plain', 'simple', 'grid', 'pipe', 'orgtbl', 'rst', 'mediawiki',
+ 'latex', and 'latex_booktabs'. Variable `tabulate_formats` contains the list of
+ currently supported formats.
+
+ "plain" format doesn't use any pseudographics to draw tables,
+ it separates columns with a double space:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "plain"))
+ strings numbers
+ spam 41.9999
+ eggs 451
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="plain"))
+ spam 41.9999
+ eggs 451
+
+ "simple" format is like Pandoc simple_tables:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "simple"))
+ strings numbers
+ --------- ---------
+ spam 41.9999
+ eggs 451
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="simple"))
+ ---- --------
+ spam 41.9999
+ eggs 451
+ ---- --------
+
+ "grid" is similar to tables produced by Emacs table.el package or
+ Pandoc grid_tables:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "grid"))
+ +-----------+-----------+
+ | strings | numbers |
+ +===========+===========+
+ | spam | 41.9999 |
+ +-----------+-----------+
+ | eggs | 451 |
+ +-----------+-----------+
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="grid"))
+ +------+----------+
+ | spam | 41.9999 |
+ +------+----------+
+ | eggs | 451 |
+ +------+----------+
+
+ "fancy_grid" draws a grid using box-drawing characters:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "fancy_grid"))
+ ╒═══════════╤═══════════╕
+ │ strings │ numbers │
+ ╞═══════════╪═══════════╡
+ │ spam │ 41.9999 │
+ ├───────────┼───────────┤
+ │ eggs │ 451 │
+ ╘═══════════╧═══════════╛
+
+ "pipe" is like tables in PHP Markdown Extra extension or Pandoc
+ pipe_tables:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "pipe"))
+ | strings | numbers |
+ |:----------|----------:|
+ | spam | 41.9999 |
+ | eggs | 451 |
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe"))
+ |:-----|---------:|
+ | spam | 41.9999 |
+ | eggs | 451 |
+
+ "orgtbl" is like tables in Emacs org-mode and orgtbl-mode. They
+ are slightly different from "pipe" format by not using colons to
+ define column alignment, and using a "+" sign to indicate line
+ intersections:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "orgtbl"))
+ | strings | numbers |
+ |-----------+-----------|
+ | spam | 41.9999 |
+ | eggs | 451 |
+
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="orgtbl"))
+ | spam | 41.9999 |
+ | eggs | 451 |
+
+ "rst" is like a simple table format from reStructuredText; please
+ note that reStructuredText accepts also "grid" tables:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "rst"))
+ ========= =========
+ strings numbers
+ ========= =========
+ spam 41.9999
+ eggs 451
+ ========= =========
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="rst"))
+ ==== ========
+ spam 41.9999
+ eggs 451
+ ==== ========
+
+ "mediawiki" produces a table markup used in Wikipedia and on other
+ MediaWiki-based sites:
+
+ >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
+ ... headers="firstrow", tablefmt="mediawiki"))
+ {| class="wikitable" style="text-align: left;"
+ |+ <!-- caption -->
+ |-
+ ! strings !! align="right"| numbers
+ |-
+ | spam || align="right"| 41.9999
+ |-
+ | eggs || align="right"| 451
+ |}
+
+ "html" produces HTML markup:
+
+ >>> print(tabulate([["strings", "numbers"], ["spam", 41.9999], ["eggs", "451.0"]],
+ ... headers="firstrow", tablefmt="html"))
+ <table>
+ <thead>
+ <tr><th>strings </th><th style="text-align: right;"> numbers</th></tr>
+ </thead>
+ <tbody>
+ <tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr>
+ <tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr>
+ </tbody>
+ </table>
+
+ "latex" produces a tabular environment of LaTeX document markup:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex"))
+ \\begin{tabular}{lr}
+ \\hline
+ spam & 41.9999 \\\\
+ eggs & 451 \\\\
+ \\hline
+ \\end{tabular}
+
+ "latex_booktabs" produces a tabular environment of LaTeX document markup
+ using the booktabs.sty package:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="latex_booktabs"))
+ \\begin{tabular}{lr}
+ \\toprule
+ spam & 41.9999 \\\\
+ eggs & 451 \\\\
+ \\bottomrule
+ \end{tabular}
+
+ Number parsing
+ --------------
+ By default, anything which can be parsed as a number is a number.
+ This ensures numbers represented as strings are aligned properly.
+ This can lead to weird results for particular strings such as
+ specific git SHAs e.g. "42992e1" will be parsed into the number
+ 429920 and aligned as such.
+
+ To completely disable number parsing (and alignment), use
+ `disable_numparse=True`. For more fine grained control, a list column
+ indices is used to disable number parsing only on those columns
+ e.g. `disable_numparse=[0, 2]` would disable number parsing only on the
+ first and third columns.
+ """
+ if tabular_data is None:
+ tabular_data = []
+ list_of_lists, headers = _normalize_tabular_data(
+ tabular_data, headers, showindex=showindex)
+
+ # empty values in the first column of RST tables should be escaped (issue #82)
+ # "" should be escaped as "\\ " or ".."
+ if tablefmt == 'rst':
+ list_of_lists, headers = _rst_escape_first_column(list_of_lists, headers)
+
+ # optimization: look for ANSI control codes once,
+ # enable smart width functions only if a control code is found
+ plain_text = '\n'.join(['\t'.join(map(_text_type, headers))] + \
+ ['\t'.join(map(_text_type, row)) for row in list_of_lists])
+
+ has_invisible = re.search(_invisible_codes, plain_text)
+ enable_widechars = wcwidth is not None and WIDE_CHARS_MODE
+ if has_invisible:
+ width_fn = _visible_width
+ elif enable_widechars: # optional wide-character support if available
+ width_fn = wcwidth.wcswidth
+ else:
+ width_fn = len
+
+ # format rows and columns, convert numeric values to strings
+ cols = list(izip_longest(*list_of_lists))
+ numparses = _expand_numparse(disable_numparse, len(cols))
+ coltypes = [_column_type(col, numparse=np) for col, np in
+ zip(cols, numparses)]
+ cols = [[_format(v, ct, floatfmt, missingval, has_invisible) for v in c]
+ for c,ct in zip(cols, coltypes)]
+
+ # align columns
+ aligns = [numalign if ct in [int,float] else stralign for ct in coltypes]
+ minwidths = [width_fn(h) + MIN_PADDING for h in headers] if headers else [0]*len(cols)
+ cols = [_align_column(c, a, minw, has_invisible)
+ for c, a, minw in zip(cols, aligns, minwidths)]
+
+ if headers:
+ # align headers and add headers
+ t_cols = cols or [['']] * len(headers)
+ t_aligns = aligns or [stralign] * len(headers)
+ minwidths = [max(minw, width_fn(c[0])) for minw, c in zip(minwidths, t_cols)]
+ headers = [_align_header(h, a, minw, width_fn(h))
+ for h, a, minw in zip(headers, t_aligns, minwidths)]
+ rows = list(zip(*cols))
+ else:
+ minwidths = [width_fn(c[0]) for c in cols]
+ rows = list(zip(*cols))
+
+ if not isinstance(tablefmt, TableFormat):
+ tablefmt = _table_formats.get(tablefmt, _table_formats["simple"])
+
+ return _format_table(tablefmt, headers, rows, minwidths, aligns)
+
+
+def _expand_numparse(disable_numparse, column_count):
+ """
+ Return a list of bools of length `column_count` which indicates whether
+ number parsing should be used on each column.
+ If `disable_numparse` is a list of indices, each of those indices are False,
+ and everything else is True.
+ If `disable_numparse` is a bool, then the returned list is all the same.
+ """
+ if isinstance(disable_numparse, Iterable):
+ numparses = [True] * column_count
+ for index in disable_numparse:
+ numparses[index] = False
+ return numparses
+ else:
+ return [not disable_numparse] * column_count
+
+
+def _build_simple_row(padded_cells, rowfmt):
+ "Format row according to DataRow format without padding."
+ begin, sep, end = rowfmt
+ return (begin + sep.join(padded_cells) + end).rstrip()
+
+
+def _build_row(padded_cells, colwidths, colaligns, rowfmt):
+ "Return a string which represents a row of data cells."
+ if not rowfmt:
+ return None
+ if hasattr(rowfmt, "__call__"):
+ return rowfmt(padded_cells, colwidths, colaligns)
+ else:
+ return _build_simple_row(padded_cells, rowfmt)
+
+
+def _build_line(colwidths, colaligns, linefmt):
+ "Return a string which represents a horizontal line."
+ if not linefmt:
+ return None
+ if hasattr(linefmt, "__call__"):
+ return linefmt(colwidths, colaligns)
+ else:
+ begin, fill, sep, end = linefmt
+ cells = [fill*w for w in colwidths]
+ return _build_simple_row(cells, (begin, sep, end))
+
+
+def _pad_row(cells, padding):
+ if cells:
+ pad = " "*padding
+ padded_cells = [pad + cell + pad for cell in cells]
+ return padded_cells
+ else:
+ return cells
+
+
+def _format_table(fmt, headers, rows, colwidths, colaligns):
+ """Produce a plain-text representation of the table."""
+ lines = []
+ hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else []
+ pad = fmt.padding
+ headerrow = fmt.headerrow
+
+ padded_widths = [(w + 2*pad) for w in colwidths]
+ padded_headers = _pad_row(headers, pad)
+ padded_rows = [_pad_row(row, pad) for row in rows]
+
+ if fmt.lineabove and "lineabove" not in hidden:
+ lines.append(_build_line(padded_widths, colaligns, fmt.lineabove))
+
+ if padded_headers:
+ lines.append(_build_row(padded_headers, padded_widths, colaligns, headerrow))
+ if fmt.linebelowheader and "linebelowheader" not in hidden:
+ lines.append(_build_line(padded_widths, colaligns, fmt.linebelowheader))
+
+ if padded_rows and fmt.linebetweenrows and "linebetweenrows" not in hidden:
+ # initial rows with a line below
+ for row in padded_rows[:-1]:
+ lines.append(_build_row(row, padded_widths, colaligns, fmt.datarow))
+ lines.append(_build_line(padded_widths, colaligns, fmt.linebetweenrows))
+ # the last row without a line below
+ lines.append(_build_row(padded_rows[-1], padded_widths, colaligns, fmt.datarow))
+ else:
+ for row in padded_rows:
+ lines.append(_build_row(row, padded_widths, colaligns, fmt.datarow))
+
+ if fmt.linebelow and "linebelow" not in hidden:
+ lines.append(_build_line(padded_widths, colaligns, fmt.linebelow))
+
+ if headers or rows:
+ return "\n".join(lines)
+ else: # a completely empty table
+ return ""
+
+
+def _main():
+ """\
+ Usage: tabulate [options] [FILE ...]
+
+ Pretty-print tabular data.
+ See also https://bitbucket.org/astanin/python-tabulate
+
+ FILE a filename of the file with tabular data;
+ if "-" or missing, read data from stdin.
+
+ Options:
+
+ -h, --help show this message
+ -1, --header use the first row of data as a table header
+ -o FILE, --output FILE print table to FILE (default: stdout)
+ -s REGEXP, --sep REGEXP use a custom column separator (default: whitespace)
+ -F FPFMT, --float FPFMT floating point number format (default: g)
+ -f FMT, --format FMT set output table format; supported formats:
+ plain, simple, grid, fancy_grid, pipe, orgtbl,
+ rst, mediawiki, html, latex, latex_booktabs, tsv
+ (default: simple)
+ """
+ import getopt
+ import sys
+ import textwrap
+ usage = textwrap.dedent(_main.__doc__)
+ try:
+ opts, args = getopt.getopt(sys.argv[1:],
+ "h1o:s:F:f:",
+ ["help", "header", "output", "sep=", "float=", "format="])
+ except getopt.GetoptError as e:
+ print(e)
+ print(usage)
+ sys.exit(2)
+ headers = []
+ floatfmt = "g"
+ tablefmt = "simple"
+ sep = r"\s+"
+ outfile = "-"
+ for opt, value in opts:
+ if opt in ["-1", "--header"]:
+ headers = "firstrow"
+ elif opt in ["-o", "--output"]:
+ outfile = value
+ elif opt in ["-F", "--float"]:
+ floatfmt = value
+ elif opt in ["-f", "--format"]:
+ if value not in tabulate_formats:
+ print("%s is not a supported table format" % value)
+ print(usage)
+ sys.exit(3)
+ tablefmt = value
+ elif opt in ["-s", "--sep"]:
+ sep = value
+ elif opt in ["-h", "--help"]:
+ print(usage)
+ sys.exit(0)
+ files = [sys.stdin] if not args else args
+ with (sys.stdout if outfile == "-" else open(outfile, "w")) as out:
+ for f in files:
+ if f == "-":
+ f = sys.stdin
+ if _is_file(f):
+ _pprint_file(f, headers=headers, tablefmt=tablefmt,
+ sep=sep, floatfmt=floatfmt, file=out)
+ else:
+ with open(f) as fobj:
+ _pprint_file(fobj, headers=headers, tablefmt=tablefmt,
+ sep=sep, floatfmt=floatfmt, file=out)
+
+
+def _pprint_file(fobject, headers, tablefmt, sep, floatfmt, file):
+ rows = fobject.readlines()
+ table = [re.split(sep, r.rstrip()) for r in rows if r.strip()]
+ print(tabulate(table, headers, tablefmt, floatfmt=floatfmt), file=file)
+
+
+if __name__ == "__main__":
+ _main()
diff --git a/test/common.py b/test/common.py deleted file mode 100644 index fe44f79..0000000 --- a/test/common.py +++ /dev/null @@ -1,31 +0,0 @@ -try: - from nose.tools import assert_equal, assert_in, assert_raises - - -except ImportError: - def assert_equal(expected, result): - print("Expected:\n%s\n" % expected) - print("Got:\n%s\n" % result) - assert expected == result - - - def assert_in(result, expected_set): - nums = xrange(1, len(expected_set)+1) - for i, expected in zip(nums, expected_set): - print("Expected %d:\n%s\n" % (i, expected)) - print("Got:\n%s\n" % result) - assert result in expected_set - - - class assert_raises(object): - def __init__(self, exception_type): - self.watch_exception_type = exception_type - def __enter__(self): - pass - def __exit__(self, exception_type, exception_value, traceback): - if isinstance(exception_value, self.watch_exception_type): - return True # suppress exception - elif exception_type is None: - msg = "%s not raised" % self.watch_exception_type.__name__ - raise AssertionError(msg) - # otherwise propagate whatever other exception is raised diff --git a/test/test_api.py b/test/test_api.py index c19d722..d8e6dad 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -1,57 +1,55 @@ -"""API properties. - -""" - -from __future__ import print_function -from __future__ import unicode_literals -from tabulate import tabulate, tabulate_formats, simple_separated_format -from platform import python_version_tuple -from nose.plugins.skip import SkipTest - - -try: - if python_version_tuple() >= ('3','3','0'): - from inspect import signature, _empty - else: - from funcsigs import signature, _empty -except ImportError: - signature = None - _empty = None - - -def test_tabulate_formats(): - "API: tabulate_formats is a list of strings""" - supported = tabulate_formats - print("tabulate_formats = %r" % supported) - assert type(supported) is list - for fmt in supported: - assert type(fmt) is type("") - - -def _check_signature(function, expected_sig): - if not signature: - raise SkipTest() - actual_sig = signature(function) - print("expected: %s\nactual: %s\n" % (expected_sig, str(actual_sig))) - for (e, ev), (a, av) in zip(expected_sig, actual_sig.parameters.items()): - assert e == a and ev == av.default - - -def test_tabulate_signature(): - "API: tabulate() type signature is unchanged""" - assert type(tabulate) is type(lambda: None) - expected_sig = [("tabular_data", _empty), - ("headers", ()), - ("tablefmt", "simple"), - ("floatfmt", "g"), - ("numalign", "decimal"), - ("stralign", "left"), - ("missingval", "")] - _check_signature(tabulate, expected_sig) - - -def test_simple_separated_format_signature(): - "API: simple_separated_format() type signature is unchanged""" - assert type(simple_separated_format) is type(lambda: None) - expected_sig = [("separator", _empty)] - _check_signature(simple_separated_format, expected_sig) +"""API properties.
+
+"""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from tabulate import tabulate, tabulate_formats, simple_separated_format
+from platform import python_version_tuple
+from common import SkipTest
+
+
+try:
+ if python_version_tuple() >= ('3','3','0'):
+ from inspect import signature, _empty
+except ImportError:
+ signature = None
+ _empty = None
+
+
+def test_tabulate_formats():
+ "API: tabulate_formats is a list of strings"""
+ supported = tabulate_formats
+ print("tabulate_formats = %r" % supported)
+ assert type(supported) is list
+ for fmt in supported:
+ assert type(fmt) is type("")
+
+
+def _check_signature(function, expected_sig):
+ if not signature:
+ raise SkipTest()
+ actual_sig = signature(function)
+ print("expected: %s\nactual: %s\n" % (expected_sig, str(actual_sig)))
+ for (e, ev), (a, av) in zip(expected_sig, actual_sig.parameters.items()):
+ assert e == a and ev == av.default
+
+
+def test_tabulate_signature():
+ "API: tabulate() type signature is unchanged"""
+ assert type(tabulate) is type(lambda: None)
+ expected_sig = [("tabular_data", _empty),
+ ("headers", ()),
+ ("tablefmt", "simple"),
+ ("floatfmt", "g"),
+ ("numalign", "decimal"),
+ ("stralign", "left"),
+ ("missingval", "")]
+ _check_signature(tabulate, expected_sig)
+
+
+def test_simple_separated_format_signature():
+ "API: simple_separated_format() type signature is unchanged"""
+ assert type(simple_separated_format) is type(lambda: None)
+ expected_sig = [("separator", _empty)]
+ _check_signature(simple_separated_format, expected_sig)
diff --git a/test/test_cli.py b/test/test_cli.py index 3d62cb6..043e823 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -1,190 +1,190 @@ -"""Command-line interface. - -""" - - -from __future__ import print_function -from __future__ import unicode_literals -import os - - -import subprocess -import tempfile - - -from common import assert_equal - - -SAMPLE_SIMPLE_FORMAT = "\n".join( - ['----- ------ -------------', - 'Sun 696000 1.9891e+09', - 'Earth 6371 5973.6', - 'Moon 1737 73.5', - 'Mars 3390 641.85', - '----- ------ -------------']) - - -SAMPLE_SIMPLE_FORMAT_WITH_HEADERS = "\n".join( - ['Planet Radius Mass', - '-------- -------- -------------', - 'Sun 696000 1.9891e+09', - 'Earth 6371 5973.6', - 'Moon 1737 73.5', - 'Mars 3390 641.85']) - - -SAMPLE_GRID_FORMAT_WITH_HEADERS = "\n".join( - ['+----------+----------+---------------+', - '| Planet | Radius | Mass |', - '+==========+==========+===============+', - '| Sun | 696000 | 1.9891e+09 |', - '+----------+----------+---------------+', - '| Earth | 6371 | 5973.6 |', - '+----------+----------+---------------+', - '| Moon | 1737 | 73.5 |', - '+----------+----------+---------------+', - '| Mars | 3390 | 641.85 |', - '+----------+----------+---------------+']) - - -SAMPLE_GRID_FORMAT_WITH_DOT1E_FLOATS = "\n".join( - ['+-------+--------+---------+', - '| Sun | 696000 | 2.0e+09 |', - '+-------+--------+---------+', - '| Earth | 6371 | 6.0e+03 |', - '+-------+--------+---------+', - '| Moon | 1737 | 7.4e+01 |', - '+-------+--------+---------+', - '| Mars | 3390 | 6.4e+02 |', - '+-------+--------+---------+']) - - -def sample_input(sep=' ', with_headers=False): - headers = sep.join(['Planet', 'Radius', 'Mass']) - rows = [sep.join(['Sun', '696000', '1.9891e9']), - sep.join(['Earth', '6371', '5973.6']), - sep.join(['Moon', '1737', '73.5']), - sep.join(['Mars', '3390', '641.85'])] - all_rows = ([headers] + rows) if with_headers else rows - table = "\n".join(all_rows) - return table - - -def run_and_capture_stdout(cmd, input=None): - x = subprocess.Popen(cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - input_buf = input.encode() if input else None - out, err = x.communicate(input=input_buf) - out = out.decode("utf-8") - if x.returncode != 0: - raise IOError(err) - return out - - -class TemporaryTextFile(object): - def __init__(self): - self.tmpfile = None - def __enter__(self): - self.tmpfile = tempfile.NamedTemporaryFile("w+", prefix="tabulate-test-tmp-", delete=False) - return self.tmpfile - def __exit__(self, exc_type, exc_val, exc_tb): - if self.tmpfile: - self.tmpfile.close() - os.unlink(self.tmpfile.name) - - -def test_script_from_stdin_to_stdout(): - """Command line utility: read from stdin, print to stdout""" - cmd = ["python", "tabulate.py"] - out = run_and_capture_stdout(cmd, input=sample_input()) - expected = SAMPLE_SIMPLE_FORMAT - print("got: ",repr(out)) - print("expected:",repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - - -def test_script_from_file_to_stdout(): - """Command line utility: read from file, print to stdout""" - with TemporaryTextFile() as tmpfile: - tmpfile.write(sample_input()) - tmpfile.seek(0) - cmd = ["python", "tabulate.py", tmpfile.name] - out = run_and_capture_stdout(cmd) - expected = SAMPLE_SIMPLE_FORMAT - print("got: ",repr(out)) - print("expected:",repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - - -def test_script_from_file_to_file(): - """Command line utility: read from file, write to file""" - with TemporaryTextFile() as input_file: - with TemporaryTextFile() as output_file: - input_file.write(sample_input()) - input_file.seek(0) - cmd = ["python", "tabulate.py", "-o", output_file.name, input_file.name] - out = run_and_capture_stdout(cmd) - # check that nothing is printed to stdout - expected = "" - print("got: ", repr(out)) - print("expected:", repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - # check that the output was written to file - output_file.seek(0) - out = output_file.file.read() - expected = SAMPLE_SIMPLE_FORMAT - print("got: ", repr(out)) - print("expected:", repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - - -def test_script_header_option(): - """Command line utility: -1, --header option""" - for option in ["-1", "--header"]: - cmd = ["python", "tabulate.py", option] - raw_table = sample_input(with_headers=True) - out = run_and_capture_stdout(cmd, input=raw_table) - expected = SAMPLE_SIMPLE_FORMAT_WITH_HEADERS - print(out) - print("got: ",repr(out)) - print("expected:",repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - - -def test_script_sep_option(): - """Command line utility: -s, --sep option""" - for option in ["-s", "--sep"]: - cmd = ["python", "tabulate.py", option, ","] - raw_table = sample_input(sep=",") - out = run_and_capture_stdout(cmd, input=raw_table) - expected = SAMPLE_SIMPLE_FORMAT - print("got: ",repr(out)) - print("expected:",repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - - -def test_script_floatfmt_option(): - """Command line utility: -F, --float option""" - for option in ["-F", "--float"]: - cmd = ["python", "tabulate.py", option, ".1e", "--format", "grid"] - raw_table = sample_input() - out = run_and_capture_stdout(cmd, input=raw_table) - expected = SAMPLE_GRID_FORMAT_WITH_DOT1E_FLOATS - print("got: ",repr(out)) - print("expected:",repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) - - -def test_script_format_option(): - """Command line utility: -f, --format option""" - for option in ["-f", "--format"]: - cmd = ["python", "tabulate.py", "-1", option, "grid"] - raw_table = sample_input(with_headers=True) - out = run_and_capture_stdout(cmd, input=raw_table) - expected = SAMPLE_GRID_FORMAT_WITH_HEADERS - print(out) - print("got: ",repr(out)) - print("expected:",repr(expected)) - assert_equal(out.splitlines(), expected.splitlines()) +"""Command-line interface.
+
+"""
+
+
+from __future__ import print_function
+from __future__ import unicode_literals
+import os
+
+
+import subprocess
+import tempfile
+
+
+from common import assert_equal
+
+
+SAMPLE_SIMPLE_FORMAT = "\n".join(
+ ['----- ------ -------------',
+ 'Sun 696000 1.9891e+09',
+ 'Earth 6371 5973.6',
+ 'Moon 1737 73.5',
+ 'Mars 3390 641.85',
+ '----- ------ -------------'])
+
+
+SAMPLE_SIMPLE_FORMAT_WITH_HEADERS = "\n".join(
+ ['Planet Radius Mass',
+ '-------- -------- -------------',
+ 'Sun 696000 1.9891e+09',
+ 'Earth 6371 5973.6',
+ 'Moon 1737 73.5',
+ 'Mars 3390 641.85'])
+
+
+SAMPLE_GRID_FORMAT_WITH_HEADERS = "\n".join(
+ ['+----------+----------+---------------+',
+ '| Planet | Radius | Mass |',
+ '+==========+==========+===============+',
+ '| Sun | 696000 | 1.9891e+09 |',
+ '+----------+----------+---------------+',
+ '| Earth | 6371 | 5973.6 |',
+ '+----------+----------+---------------+',
+ '| Moon | 1737 | 73.5 |',
+ '+----------+----------+---------------+',
+ '| Mars | 3390 | 641.85 |',
+ '+----------+----------+---------------+'])
+
+
+SAMPLE_GRID_FORMAT_WITH_DOT1E_FLOATS = "\n".join(
+ ['+-------+--------+---------+',
+ '| Sun | 696000 | 2.0e+09 |',
+ '+-------+--------+---------+',
+ '| Earth | 6371 | 6.0e+03 |',
+ '+-------+--------+---------+',
+ '| Moon | 1737 | 7.4e+01 |',
+ '+-------+--------+---------+',
+ '| Mars | 3390 | 6.4e+02 |',
+ '+-------+--------+---------+'])
+
+
+def sample_input(sep=' ', with_headers=False):
+ headers = sep.join(['Planet', 'Radius', 'Mass'])
+ rows = [sep.join(['Sun', '696000', '1.9891e9']),
+ sep.join(['Earth', '6371', '5973.6']),
+ sep.join(['Moon', '1737', '73.5']),
+ sep.join(['Mars', '3390', '641.85'])]
+ all_rows = ([headers] + rows) if with_headers else rows
+ table = "\n".join(all_rows)
+ return table
+
+
+def run_and_capture_stdout(cmd, input=None):
+ x = subprocess.Popen(cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ input_buf = input.encode() if input else None
+ out, err = x.communicate(input=input_buf)
+ out = out.decode("utf-8")
+ if x.returncode != 0:
+ raise IOError(err)
+ return out
+
+
+class TemporaryTextFile(object):
+ def __init__(self):
+ self.tmpfile = None
+ def __enter__(self):
+ self.tmpfile = tempfile.NamedTemporaryFile("w+", prefix="tabulate-test-tmp-", delete=False)
+ return self.tmpfile
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if self.tmpfile:
+ self.tmpfile.close()
+ os.unlink(self.tmpfile.name)
+
+
+def test_script_from_stdin_to_stdout():
+ """Command line utility: read from stdin, print to stdout"""
+ cmd = ["python", "tabulate.py"]
+ out = run_and_capture_stdout(cmd, input=sample_input())
+ expected = SAMPLE_SIMPLE_FORMAT
+ print("got: ",repr(out))
+ print("expected:",repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+
+
+def test_script_from_file_to_stdout():
+ """Command line utility: read from file, print to stdout"""
+ with TemporaryTextFile() as tmpfile:
+ tmpfile.write(sample_input())
+ tmpfile.seek(0)
+ cmd = ["python", "tabulate.py", tmpfile.name]
+ out = run_and_capture_stdout(cmd)
+ expected = SAMPLE_SIMPLE_FORMAT
+ print("got: ",repr(out))
+ print("expected:",repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+
+
+def test_script_from_file_to_file():
+ """Command line utility: read from file, write to file"""
+ with TemporaryTextFile() as input_file:
+ with TemporaryTextFile() as output_file:
+ input_file.write(sample_input())
+ input_file.seek(0)
+ cmd = ["python", "tabulate.py", "-o", output_file.name, input_file.name]
+ out = run_and_capture_stdout(cmd)
+ # check that nothing is printed to stdout
+ expected = ""
+ print("got: ", repr(out))
+ print("expected:", repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+ # check that the output was written to file
+ output_file.seek(0)
+ out = output_file.file.read()
+ expected = SAMPLE_SIMPLE_FORMAT
+ print("got: ", repr(out))
+ print("expected:", repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+
+
+def test_script_header_option():
+ """Command line utility: -1, --header option"""
+ for option in ["-1", "--header"]:
+ cmd = ["python", "tabulate.py", option]
+ raw_table = sample_input(with_headers=True)
+ out = run_and_capture_stdout(cmd, input=raw_table)
+ expected = SAMPLE_SIMPLE_FORMAT_WITH_HEADERS
+ print(out)
+ print("got: ",repr(out))
+ print("expected:",repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+
+
+def test_script_sep_option():
+ """Command line utility: -s, --sep option"""
+ for option in ["-s", "--sep"]:
+ cmd = ["python", "tabulate.py", option, ","]
+ raw_table = sample_input(sep=",")
+ out = run_and_capture_stdout(cmd, input=raw_table)
+ expected = SAMPLE_SIMPLE_FORMAT
+ print("got: ",repr(out))
+ print("expected:",repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+
+
+def test_script_floatfmt_option():
+ """Command line utility: -F, --float option"""
+ for option in ["-F", "--float"]:
+ cmd = ["python", "tabulate.py", option, ".1e", "--format", "grid"]
+ raw_table = sample_input()
+ out = run_and_capture_stdout(cmd, input=raw_table)
+ expected = SAMPLE_GRID_FORMAT_WITH_DOT1E_FLOATS
+ print("got: ",repr(out))
+ print("expected:",repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
+
+
+def test_script_format_option():
+ """Command line utility: -f, --format option"""
+ for option in ["-f", "--format"]:
+ cmd = ["python", "tabulate.py", "-1", option, "grid"]
+ raw_table = sample_input(with_headers=True)
+ out = run_and_capture_stdout(cmd, input=raw_table)
+ expected = SAMPLE_GRID_FORMAT_WITH_HEADERS
+ print(out)
+ print("got: ",repr(out))
+ print("expected:",repr(expected))
+ assert_equal(out.splitlines(), expected.splitlines())
diff --git a/test/test_input.py b/test/test_input.py index 50c4c8f..d44fe1d 100644 --- a/test/test_input.py +++ b/test/test_input.py @@ -1,403 +1,450 @@ -# -*- coding: utf-8 -*- - -"""Test support of the various forms of tabular data.""" - -from __future__ import print_function -from __future__ import unicode_literals -from tabulate import tabulate -from common import assert_equal, assert_in, assert_raises -from nose.plugins.skip import SkipTest - - -def test_iterable_of_iterables(): - "Input: an interable of iterables." - ii = iter(map(lambda x: iter(x), [range(5), range(5,0,-1)])) - expected = "\n".join( - ['- - - - -', - '0 1 2 3 4', - '5 4 3 2 1', - '- - - - -']) - result = tabulate(ii) - assert_equal(expected, result) - - -def test_iterable_of_iterables_headers(): - "Input: an interable of iterables with headers." - ii = iter(map(lambda x: iter(x), [range(5), range(5,0,-1)])) - expected = "\n".join( - [' a b c d e', - '--- --- --- --- ---', - ' 0 1 2 3 4', - ' 5 4 3 2 1']) - result = tabulate(ii, "abcde") - assert_equal(expected, result) - - -def test_iterable_of_iterables_firstrow(): - "Input: an interable of iterables with the first row as headers" - ii = iter(map(lambda x: iter(x), ["abcde", range(5), range(5,0,-1)])) - expected = "\n".join( - [' a b c d e', - '--- --- --- --- ---', - ' 0 1 2 3 4', - ' 5 4 3 2 1']) - result = tabulate(ii, "firstrow") - assert_equal(expected, result) - - -def test_list_of_lists(): - "Input: a list of lists with headers." - ll = [["a","one",1],["b","two",None]] - expected = "\n".join([ - ' string number', - '-- -------- --------', - 'a one 1', - 'b two']) - result = tabulate(ll, headers=["string","number"]) - assert_equal(expected, result) - - -def test_list_of_lists_firstrow(): - "Input: a list of lists with the first row as headers." - ll = [["string","number"],["a","one",1],["b","two",None]] - expected = "\n".join([ - ' string number', - '-- -------- --------', - 'a one 1', - 'b two']) - result = tabulate(ll, headers="firstrow") - assert_equal(expected, result) - - -def test_list_of_lists_keys(): - "Input: a list of lists with column indices as headers." - ll = [["a","one",1],["b","two",None]] - expected = "\n".join([ - '0 1 2', - '--- --- ---', - 'a one 1', - 'b two']) - result = tabulate(ll, headers="keys") - assert_equal(expected, result) - - -def test_dict_like(): - "Input: a dict of iterables with keys as headers." - # columns should be padded with None, keys should be used as headers - dd = {"a": range(3), "b": range(101,105)} - # keys' order (hence columns' order) is not deterministic in Python 3 - # => we have to consider both possible results as valid - expected1 = "\n".join([ - ' a b', - '--- ---', - ' 0 101', - ' 1 102', - ' 2 103', - ' 104']) - expected2 = "\n".join([ - ' b a', - '--- ---', - '101 0', - '102 1', - '103 2', - '104']) - result = tabulate(dd, "keys") - print("Keys' order: %s" % dd.keys()) - assert_in(result, [expected1, expected2]) - - -def test_numpy_2d(): - "Input: a two-dimensional NumPy array with headers." - try: - import numpy - na = (numpy.arange(1,10, dtype=numpy.float32).reshape((3,3))**3)*0.5 - expected = "\n".join([ - ' a b c', - '----- ----- -----', - ' 0.5 4 13.5', - ' 32 62.5 108', - '171.5 256 364.5']) - result = tabulate(na, ["a", "b", "c"]) - assert_equal(expected, result) - except ImportError: - print("test_numpy_2d is skipped") - raise SkipTest() # this test is optional - - -def test_numpy_2d_firstrow(): - "Input: a two-dimensional NumPy array with the first row as headers." - try: - import numpy - na = (numpy.arange(1,10, dtype=numpy.int32).reshape((3,3))**3) - expected = "\n".join([ - ' 1 8 27', - '--- --- ----', - ' 64 125 216', - '343 512 729']) - result = tabulate(na, headers="firstrow") - assert_equal(expected, result) - except ImportError: - print("test_numpy_2d_firstrow is skipped") - raise SkipTest() # this test is optional - - - -def test_numpy_2d_keys(): - "Input: a two-dimensional NumPy array with column indices as headers." - try: - import numpy - na = (numpy.arange(1,10, dtype=numpy.float32).reshape((3,3))**3)*0.5 - expected = "\n".join([ - ' 0 1 2', - '----- ----- -----', - ' 0.5 4 13.5', - ' 32 62.5 108', - '171.5 256 364.5']) - result = tabulate(na, headers="keys") - assert_equal(expected, result) - except ImportError: - print("test_numpy_2d_keys is skipped") - raise SkipTest() # this test is optional - - -def test_numpy_record_array(): - "Input: a two-dimensional NumPy record array without header." - try: - import numpy - na = numpy.asarray([("Alice", 23, 169.5), - ("Bob", 27, 175.0)], - dtype={"names":["name","age","height"], - "formats":["a32","uint8","float32"]}) - expected = "\n".join([ - "----- -- -----", - "Alice 23 169.5", - "Bob 27 175", - "----- -- -----" ]) - result = tabulate(na) - assert_equal(expected, result) - except ImportError: - print("test_numpy_2d_keys is skipped") - raise SkipTest() # this test is optional - - -def test_numpy_record_array_keys(): - "Input: a two-dimensional NumPy record array with column names as headers." - try: - import numpy - na = numpy.asarray([("Alice", 23, 169.5), - ("Bob", 27, 175.0)], - dtype={"names":["name","age","height"], - "formats":["a32","uint8","float32"]}) - expected = "\n".join([ - "name age height", - "------ ----- --------", - "Alice 23 169.5", - "Bob 27 175" ]) - result = tabulate(na, headers="keys") - assert_equal(expected, result) - except ImportError: - print("test_numpy_2d_keys is skipped") - raise SkipTest() # this test is optional - - -def test_numpy_record_array_headers(): - "Input: a two-dimensional NumPy record array with user-supplied headers." - try: - import numpy - na = numpy.asarray([("Alice", 23, 169.5), - ("Bob", 27, 175.0)], - dtype={"names":["name","age","height"], - "formats":["a32","uint8","float32"]}) - expected = "\n".join([ - "person years cm", - "-------- ------- -----", - "Alice 23 169.5", - "Bob 27 175" ]) - result = tabulate(na, headers=["person", "years", "cm"]) - assert_equal(expected, result) - except ImportError: - print("test_numpy_2d_keys is skipped") - raise SkipTest() # this test is optional - - -def test_pandas(): - "Input: a Pandas DataFrame." - try: - import pandas - df = pandas.DataFrame([["one",1],["two",None]], index=["a","b"]) - expected = "\n".join([ - ' string number', - '-- -------- --------', - 'a one 1', - 'b two nan']) - result = tabulate(df, headers=["string", "number"]) - assert_equal(expected, result) - except ImportError: - print("test_pandas is skipped") - raise SkipTest() # this test is optional - - -def test_pandas_firstrow(): - "Input: a Pandas DataFrame with the first row as headers." - try: - import pandas - df = pandas.DataFrame([["one",1],["two",None]], - columns=["string","number"], - index=["a","b"]) - expected = "\n".join([ - 'a one 1.0', - '--- ----- -----', - 'b two nan']) - result = tabulate(df, headers="firstrow") - assert_equal(expected, result) - except ImportError: - print("test_pandas_firstrow is skipped") - raise SkipTest() # this test is optional - - -def test_pandas_keys(): - "Input: a Pandas DataFrame with keys as headers." - try: - import pandas - df = pandas.DataFrame([["one",1],["two",None]], - columns=["string","number"], - index=["a","b"]) - expected = "\n".join( - [' string number', - '-- -------- --------', - 'a one 1', - 'b two nan']) - result = tabulate(df, headers="keys") - assert_equal(expected, result) - except ImportError: - print("test_pandas_keys is skipped") - raise SkipTest() # this test is optional - - -def test_list_of_namedtuples(): - "Input: a list of named tuples with field names as headers." - from collections import namedtuple - NT = namedtuple("NT", ['foo', 'bar']) - lt = [NT(1,2), NT(3,4)] - expected = "\n".join([ - '- -', - '1 2', - '3 4', - '- -']) - result = tabulate(lt) - assert_equal(expected, result) - - -def test_list_of_namedtuples_keys(): - "Input: a list of named tuples with field names as headers." - from collections import namedtuple - NT = namedtuple("NT", ['foo', 'bar']) - lt = [NT(1,2), NT(3,4)] - expected = "\n".join([ - ' foo bar', - '----- -----', - ' 1 2', - ' 3 4']) - result = tabulate(lt, headers="keys") - assert_equal(expected, result) - - -def test_list_of_dicts(): - "Input: a list of dictionaries." - lod = [{'foo' : 1, 'bar' : 2}, {'foo' : 3, 'bar' : 4}] - expected1 = "\n".join([ - '- -', - '1 2', - '3 4', - '- -']) - expected2 = "\n".join([ - '- -', - '2 1', - '4 3', - '- -']) - result = tabulate(lod) - assert_in(result, [expected1, expected2]) - - -def test_list_of_dicts_keys(): - "Input: a list of dictionaries, with keys as headers." - lod = [{'foo' : 1, 'bar' : 2}, {'foo' : 3, 'bar' : 4}] - expected1 = "\n".join([ - ' foo bar', - '----- -----', - ' 1 2', - ' 3 4']) - expected2 = "\n".join([ - ' bar foo', - '----- -----', - ' 2 1', - ' 4 3']) - result = tabulate(lod, headers="keys") - assert_in(result, [expected1, expected2]) - - -def test_list_of_dicts_with_missing_keys(): - "Input: a list of dictionaries, with missing keys." - lod = [{"foo": 1}, {"bar": 2}, {"foo":4, "baz": 3}] - expected = "\n".join([ - ' foo bar baz', - '----- ----- -----', - ' 1', - ' 2', - ' 4 3']) - result = tabulate(lod, headers="keys") - assert_equal(expected, result) - - -def test_list_of_dicts_firstrow(): - "Input: a list of dictionaries, with the first dict as headers." - lod = [{'foo' : "FOO", 'bar' : "BAR"}, {'foo' : 3, 'bar': 4, 'baz': 5}] - # if some key is missing in the first dict, use the key name instead - expected1 = "\n".join([ - ' FOO BAR baz', - '----- ----- -----', - ' 3 4 5']) - expected2 = "\n".join([ - ' BAR FOO baz', - '----- ----- -----', - ' 4 3 5']) - result = tabulate(lod, headers="firstrow") - assert_in(result, [expected1, expected2]) - - -def test_list_of_dicts_with_dict_of_headers(): - "Input: a dict of user headers for a list of dicts (issue #23)" - table = [{"letters": "ABCDE", "digits": 12345}] - headers = {"digits": "DIGITS", "letters": "LETTERS"} - expected1 = "\n".join([ - ' DIGITS LETTERS', - '-------- ---------', - ' 12345 ABCDE']) - expected2 = "\n".join([ - 'LETTERS DIGITS', - '--------- --------', - 'ABCDE 12345']) - result = tabulate(table, headers=headers) - assert_in(result, [expected1, expected2]) - - -def test_list_of_dicts_with_list_of_headers(): - "Input: a list of headers for a list of dicts, raise ValueError (issue #23)" - table = [{"letters": "ABCDE", "digits": 12345}] - headers = ["DIGITS", "LETTERS"] - with assert_raises(ValueError): - tabulate(table, headers=headers) - - -def test_py27orlater_list_of_ordereddicts(): - "Input: a list of OrderedDicts." - from collections import OrderedDict - od = OrderedDict([('b', 1), ('a', 2)]) - lod = [od, od] - expected = "\n".join([ - ' b a', - '--- ---', - ' 1 2', - ' 1 2']) - result = tabulate(lod, headers="keys") - assert_equal(expected, result) +# -*- coding: utf-8 -*-
+
+"""Test support of the various forms of tabular data."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from tabulate import tabulate
+from common import assert_equal, assert_in, assert_raises, SkipTest
+
+
+def test_iterable_of_iterables():
+ "Input: an interable of iterables."
+ ii = iter(map(lambda x: iter(x), [range(5), range(5,0,-1)]))
+ expected = "\n".join(
+ ['- - - - -',
+ '0 1 2 3 4',
+ '5 4 3 2 1',
+ '- - - - -'])
+ result = tabulate(ii)
+ assert_equal(expected, result)
+
+
+def test_iterable_of_iterables_headers():
+ "Input: an interable of iterables with headers."
+ ii = iter(map(lambda x: iter(x), [range(5), range(5,0,-1)]))
+ expected = "\n".join(
+ [' a b c d e',
+ '--- --- --- --- ---',
+ ' 0 1 2 3 4',
+ ' 5 4 3 2 1'])
+ result = tabulate(ii, "abcde")
+ assert_equal(expected, result)
+
+
+def test_iterable_of_iterables_firstrow():
+ "Input: an interable of iterables with the first row as headers"
+ ii = iter(map(lambda x: iter(x), ["abcde", range(5), range(5,0,-1)]))
+ expected = "\n".join(
+ [' a b c d e',
+ '--- --- --- --- ---',
+ ' 0 1 2 3 4',
+ ' 5 4 3 2 1'])
+ result = tabulate(ii, "firstrow")
+ assert_equal(expected, result)
+
+
+def test_list_of_lists():
+ "Input: a list of lists with headers."
+ ll = [["a","one",1],["b","two",None]]
+ expected = "\n".join([
+ ' string number',
+ '-- -------- --------',
+ 'a one 1',
+ 'b two'])
+ result = tabulate(ll, headers=["string","number"])
+ assert_equal(expected, result)
+
+
+def test_list_of_lists_firstrow():
+ "Input: a list of lists with the first row as headers."
+ ll = [["string","number"],["a","one",1],["b","two",None]]
+ expected = "\n".join([
+ ' string number',
+ '-- -------- --------',
+ 'a one 1',
+ 'b two'])
+ result = tabulate(ll, headers="firstrow")
+ assert_equal(expected, result)
+
+
+def test_list_of_lists_keys():
+ "Input: a list of lists with column indices as headers."
+ ll = [["a","one",1],["b","two",None]]
+ expected = "\n".join([
+ '0 1 2',
+ '--- --- ---',
+ 'a one 1',
+ 'b two'])
+ result = tabulate(ll, headers="keys")
+ assert_equal(expected, result)
+
+
+def test_dict_like():
+ "Input: a dict of iterables with keys as headers."
+ # columns should be padded with None, keys should be used as headers
+ dd = {"a": range(3), "b": range(101,105)}
+ # keys' order (hence columns' order) is not deterministic in Python 3
+ # => we have to consider both possible results as valid
+ expected1 = "\n".join([
+ ' a b',
+ '--- ---',
+ ' 0 101',
+ ' 1 102',
+ ' 2 103',
+ ' 104'])
+ expected2 = "\n".join([
+ ' b a',
+ '--- ---',
+ '101 0',
+ '102 1',
+ '103 2',
+ '104'])
+ result = tabulate(dd, "keys")
+ print("Keys' order: %s" % dd.keys())
+ assert_in(result, [expected1, expected2])
+
+
+def test_numpy_2d():
+ "Input: a 2D NumPy array with headers."
+ try:
+ import numpy
+ na = (numpy.arange(1,10, dtype=numpy.float32).reshape((3,3))**3)*0.5
+ expected = "\n".join([
+ ' a b c',
+ '----- ----- -----',
+ ' 0.5 4 13.5',
+ ' 32 62.5 108',
+ '171.5 256 364.5'])
+ result = tabulate(na, ["a", "b", "c"])
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_numpy_2d is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_numpy_2d_firstrow():
+ "Input: a 2D NumPy array with the first row as headers."
+ try:
+ import numpy
+ na = (numpy.arange(1,10, dtype=numpy.int32).reshape((3,3))**3)
+ expected = "\n".join([
+ ' 1 8 27',
+ '--- --- ----',
+ ' 64 125 216',
+ '343 512 729'])
+ result = tabulate(na, headers="firstrow")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_numpy_2d_firstrow is skipped")
+ raise SkipTest() # this test is optional
+
+
+
+def test_numpy_2d_keys():
+ "Input: a 2D NumPy array with column indices as headers."
+ try:
+ import numpy
+ na = (numpy.arange(1,10, dtype=numpy.float32).reshape((3,3))**3)*0.5
+ expected = "\n".join([
+ ' 0 1 2',
+ '----- ----- -----',
+ ' 0.5 4 13.5',
+ ' 32 62.5 108',
+ '171.5 256 364.5'])
+ result = tabulate(na, headers="keys")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_numpy_2d_keys is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_numpy_record_array():
+ "Input: a 2D NumPy record array without header."
+ try:
+ import numpy
+ na = numpy.asarray([("Alice", 23, 169.5),
+ ("Bob", 27, 175.0)],
+ dtype={"names":["name","age","height"],
+ "formats":["a32","uint8","float32"]})
+ expected = "\n".join([
+ "----- -- -----",
+ "Alice 23 169.5",
+ "Bob 27 175",
+ "----- -- -----" ])
+ result = tabulate(na)
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_numpy_2d_keys is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_numpy_record_array_keys():
+ "Input: a 2D NumPy record array with column names as headers."
+ try:
+ import numpy
+ na = numpy.asarray([("Alice", 23, 169.5),
+ ("Bob", 27, 175.0)],
+ dtype={"names":["name","age","height"],
+ "formats":["a32","uint8","float32"]})
+ expected = "\n".join([
+ "name age height",
+ "------ ----- --------",
+ "Alice 23 169.5",
+ "Bob 27 175" ])
+ result = tabulate(na, headers="keys")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_numpy_2d_keys is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_numpy_record_array_headers():
+ "Input: a 2D NumPy record array with user-supplied headers."
+ try:
+ import numpy
+ na = numpy.asarray([("Alice", 23, 169.5),
+ ("Bob", 27, 175.0)],
+ dtype={"names":["name","age","height"],
+ "formats":["a32","uint8","float32"]})
+ expected = "\n".join([
+ "person years cm",
+ "-------- ------- -----",
+ "Alice 23 169.5",
+ "Bob 27 175" ])
+ result = tabulate(na, headers=["person", "years", "cm"])
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_numpy_2d_keys is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_pandas():
+ "Input: a Pandas DataFrame."
+ try:
+ import pandas
+ df = pandas.DataFrame([["one",1],["two",None]], index=["a","b"])
+ expected = "\n".join([
+ ' string number',
+ '-- -------- --------',
+ 'a one 1',
+ 'b two nan'])
+ result = tabulate(df, headers=["string", "number"])
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_pandas_firstrow():
+ "Input: a Pandas DataFrame with the first row as headers."
+ try:
+ import pandas
+ df = pandas.DataFrame([["one",1],["two",None]],
+ columns=["string","number"],
+ index=["a","b"])
+ expected = "\n".join([
+ 'a one 1.0',
+ '--- ----- -----',
+ 'b two nan'])
+ result = tabulate(df, headers="firstrow")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas_firstrow is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_pandas_keys():
+ "Input: a Pandas DataFrame with keys as headers."
+ try:
+ import pandas
+ df = pandas.DataFrame([["one",1],["two",None]],
+ columns=["string","number"],
+ index=["a","b"])
+ expected = "\n".join(
+ [' string number',
+ '-- -------- --------',
+ 'a one 1',
+ 'b two nan'])
+ result = tabulate(df, headers="keys")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas_keys is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_sqlite3():
+ "Input: an sqlite3 cursor"
+ try:
+ import sqlite3
+ conn = sqlite3.connect(':memory:')
+ cursor = conn.cursor()
+ cursor.execute('CREATE TABLE people (name, age, height)')
+ for values in [
+ ("Alice", 23, 169.5),
+ ("Bob", 27, 175.0)]:
+ cursor.execute('INSERT INTO people VALUES (?, ?, ?)', values)
+ cursor.execute('SELECT name, age, height FROM people ORDER BY name')
+ result = tabulate(cursor, headers=["whom", "how old", "how tall"])
+ expected = """\
+whom how old how tall
+------ --------- ----------
+Alice 23 169.5
+Bob 27 175"""
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_sqlite3 is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_sqlite3_keys():
+ "Input: an sqlite3 cursor with keys as headers"
+ try:
+ import sqlite3
+ conn = sqlite3.connect(':memory:')
+ cursor = conn.cursor()
+ cursor.execute('CREATE TABLE people (name, age, height)')
+ for values in [
+ ("Alice", 23, 169.5),
+ ("Bob", 27, 175.0)]:
+ cursor.execute('INSERT INTO people VALUES (?, ?, ?)', values)
+ cursor.execute('SELECT name "whom", age "how old", height "how tall" FROM people ORDER BY name')
+ result = tabulate(cursor, headers="keys")
+ expected = """\
+whom how old how tall
+------ --------- ----------
+Alice 23 169.5
+Bob 27 175"""
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_sqlite3_keys is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_list_of_namedtuples():
+ "Input: a list of named tuples with field names as headers."
+ from collections import namedtuple
+ NT = namedtuple("NT", ['foo', 'bar'])
+ lt = [NT(1,2), NT(3,4)]
+ expected = "\n".join([
+ '- -',
+ '1 2',
+ '3 4',
+ '- -'])
+ result = tabulate(lt)
+ assert_equal(expected, result)
+
+
+def test_list_of_namedtuples_keys():
+ "Input: a list of named tuples with field names as headers."
+ from collections import namedtuple
+ NT = namedtuple("NT", ['foo', 'bar'])
+ lt = [NT(1,2), NT(3,4)]
+ expected = "\n".join([
+ ' foo bar',
+ '----- -----',
+ ' 1 2',
+ ' 3 4'])
+ result = tabulate(lt, headers="keys")
+ assert_equal(expected, result)
+
+
+def test_list_of_dicts():
+ "Input: a list of dictionaries."
+ lod = [{'foo' : 1, 'bar' : 2}, {'foo' : 3, 'bar' : 4}]
+ expected1 = "\n".join([
+ '- -',
+ '1 2',
+ '3 4',
+ '- -'])
+ expected2 = "\n".join([
+ '- -',
+ '2 1',
+ '4 3',
+ '- -'])
+ result = tabulate(lod)
+ assert_in(result, [expected1, expected2])
+
+
+def test_list_of_dicts_keys():
+ "Input: a list of dictionaries, with keys as headers."
+ lod = [{'foo' : 1, 'bar' : 2}, {'foo' : 3, 'bar' : 4}]
+ expected1 = "\n".join([
+ ' foo bar',
+ '----- -----',
+ ' 1 2',
+ ' 3 4'])
+ expected2 = "\n".join([
+ ' bar foo',
+ '----- -----',
+ ' 2 1',
+ ' 4 3'])
+ result = tabulate(lod, headers="keys")
+ assert_in(result, [expected1, expected2])
+
+
+def test_list_of_dicts_with_missing_keys():
+ "Input: a list of dictionaries, with missing keys."
+ lod = [{"foo": 1}, {"bar": 2}, {"foo":4, "baz": 3}]
+ expected = "\n".join([
+ ' foo bar baz',
+ '----- ----- -----',
+ ' 1',
+ ' 2',
+ ' 4 3'])
+ result = tabulate(lod, headers="keys")
+ assert_equal(expected, result)
+
+
+def test_list_of_dicts_firstrow():
+ "Input: a list of dictionaries, with the first dict as headers."
+ lod = [{'foo' : "FOO", 'bar' : "BAR"}, {'foo' : 3, 'bar': 4, 'baz': 5}]
+ # if some key is missing in the first dict, use the key name instead
+ expected1 = "\n".join([
+ ' FOO BAR baz',
+ '----- ----- -----',
+ ' 3 4 5'])
+ expected2 = "\n".join([
+ ' BAR FOO baz',
+ '----- ----- -----',
+ ' 4 3 5'])
+ result = tabulate(lod, headers="firstrow")
+ assert_in(result, [expected1, expected2])
+
+
+def test_list_of_dicts_with_dict_of_headers():
+ "Input: a dict of user headers for a list of dicts (issue #23)"
+ table = [{"letters": "ABCDE", "digits": 12345}]
+ headers = {"digits": "DIGITS", "letters": "LETTERS"}
+ expected1 = "\n".join([
+ ' DIGITS LETTERS',
+ '-------- ---------',
+ ' 12345 ABCDE'])
+ expected2 = "\n".join([
+ 'LETTERS DIGITS',
+ '--------- --------',
+ 'ABCDE 12345'])
+ result = tabulate(table, headers=headers)
+ assert_in(result, [expected1, expected2])
+
+
+def test_list_of_dicts_with_list_of_headers():
+ "Input: ValueError on a list of headers with a list of dicts (issue #23)"
+ table = [{"letters": "ABCDE", "digits": 12345}]
+ headers = ["DIGITS", "LETTERS"]
+ with assert_raises(ValueError):
+ tabulate(table, headers=headers)
+
+
+def test_py27orlater_list_of_ordereddicts():
+ "Input: a list of OrderedDicts."
+ from collections import OrderedDict
+ od = OrderedDict([('b', 1), ('a', 2)])
+ lod = [od, od]
+ expected = "\n".join([
+ ' b a',
+ '--- ---',
+ ' 1 2',
+ ' 1 2'])
+ result = tabulate(lod, headers="keys")
+ assert_equal(expected, result)
diff --git a/test/test_output.py b/test/test_output.py index 534c566..cc68cdd 100644 --- a/test/test_output.py +++ b/test/test_output.py @@ -1,349 +1,594 @@ -# -*- coding: utf-8 -*- - -"""Test output of the various forms of tabular data.""" - -from __future__ import print_function -from __future__ import unicode_literals -from tabulate import tabulate, simple_separated_format -from common import assert_equal - - -# _test_table shows -# - coercion of a string to a number, -# - left alignment of text, -# - decimal point alignment of numbers -_test_table = [["spam", 41.9999], ["eggs", "451.0"]] -_test_table_headers = ["strings", "numbers"] - - -def test_plain(): - "Output: plain with headers" - expected = "\n".join(['strings numbers', - 'spam 41.9999', - 'eggs 451',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="plain") - assert_equal(expected, result) - - -def test_plain_headerless(): - "Output: plain without headers" - expected = "\n".join(['spam 41.9999', - 'eggs 451',]) - result = tabulate(_test_table, tablefmt="plain") - assert_equal(expected, result) - - -def test_simple(): - "Output: simple with headers" - expected = "\n".join(['strings numbers', - '--------- ---------', - 'spam 41.9999', - 'eggs 451',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="simple") - assert_equal(expected, result) - - -def test_simple_headerless(): - "Output: simple without headers" - expected = "\n".join(['---- --------', - 'spam 41.9999', - 'eggs 451', - '---- --------',]) - result = tabulate(_test_table, tablefmt="simple") - assert_equal(expected, result) - - -def test_grid(): - "Output: grid with headers" - expected = '\n'.join(['+-----------+-----------+', - '| strings | numbers |', - '+===========+===========+', - '| spam | 41.9999 |', - '+-----------+-----------+', - '| eggs | 451 |', - '+-----------+-----------+',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="grid") - assert_equal(expected, result) - - -def test_grid_headerless(): - "Output: grid without headers" - expected = '\n'.join(['+------+----------+', - '| spam | 41.9999 |', - '+------+----------+', - '| eggs | 451 |', - '+------+----------+',]) - result = tabulate(_test_table, tablefmt="grid") - assert_equal(expected, result) - - -def test_fancy_grid(): - "Output: fancy_grid with headers" - expected = '\n'.join([ - '╒═══════════╤═══════════╕', - '│ strings │ numbers │', - '╞═══════════╪═══════════╡', - '│ spam │ 41.9999 │', - '├───────────┼───────────┤', - '│ eggs │ 451 │', - '╘═══════════╧═══════════╛',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="fancy_grid") - assert_equal(expected, result) - - -def test_fancy_grid_headerless(): - "Output: fancy_grid without headers" - expected = '\n'.join([ - '╒══════╤══════════╕', - '│ spam │ 41.9999 │', - '├──────┼──────────┤', - '│ eggs │ 451 │', - '╘══════╧══════════╛',]) - result = tabulate(_test_table, tablefmt="fancy_grid") - assert_equal(expected, result) - - -def test_pipe(): - "Output: pipe with headers" - expected = '\n'.join(['| strings | numbers |', - '|:----------|----------:|', - '| spam | 41.9999 |', - '| eggs | 451 |',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="pipe") - assert_equal(expected, result) - - -def test_pipe_headerless(): - "Output: pipe without headers" - expected = '\n'.join(['|:-----|---------:|', - '| spam | 41.9999 |', - '| eggs | 451 |',]) - result = tabulate(_test_table, tablefmt="pipe") - assert_equal(expected, result) - - -def test_orgtbl(): - "Output: orgtbl with headers" - expected = '\n'.join(['| strings | numbers |', - '|-----------+-----------|', - '| spam | 41.9999 |', - '| eggs | 451 |',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="orgtbl") - assert_equal(expected, result) - - -def test_orgtbl_headerless(): - "Output: orgtbl without headers" - expected = '\n'.join(['| spam | 41.9999 |', - '| eggs | 451 |',]) - result = tabulate(_test_table, tablefmt="orgtbl") - assert_equal(expected, result) - -def test_psql(): - "Output: psql with headers" - expected = '\n'.join(['+-----------+-----------+', - '| strings | numbers |', - '|-----------+-----------|', - '| spam | 41.9999 |', - '| eggs | 451 |', - '+-----------+-----------+',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="psql") - assert_equal(expected, result) - -def test_psql_headerless(): - "Output: psql without headers" - expected = '\n'.join(['+------+----------+', - '| spam | 41.9999 |', - '| eggs | 451 |', - '+------+----------+',]) - result = tabulate(_test_table, tablefmt="psql") - assert_equal(expected, result) - -def test_rst(): - "Output: rst with headers" - expected = '\n'.join(['========= =========', - 'strings numbers', - '========= =========', - 'spam 41.9999', - 'eggs 451', - '========= =========',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="rst") - assert_equal(expected, result) - - -def test_rst_headerless(): - "Output: rst without headers" - expected = '\n'.join(['==== ========', - 'spam 41.9999', - 'eggs 451', - '==== ========',]) - result = tabulate(_test_table, tablefmt="rst") - assert_equal(expected, result) - -def test_mediawiki(): - "Output: mediawiki with headers" - expected = '\n'.join(['{| class="wikitable" style="text-align: left;"', - '|+ <!-- caption -->', - '|-', - '! strings !! align="right"| numbers', - '|-', - '| spam || align="right"| 41.9999', - '|-', - '| eggs || align="right"| 451', - '|}',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="mediawiki") - assert_equal(expected, result) - - -def test_mediawiki_headerless(): - "Output: mediawiki without headers" - expected = '\n'.join(['{| class="wikitable" style="text-align: left;"', - '|+ <!-- caption -->', - '|-', - '| spam || align="right"| 41.9999', - '|-', - '| eggs || align="right"| 451', - '|}',]) - result = tabulate(_test_table, tablefmt="mediawiki") - assert_equal(expected, result) - - -def test_html(): - "Output: html with headers" - expected = '\n'.join([ - '<table>', - '<tr><th>strings </th><th style="text-align: right;"> numbers</th></tr>', - '<tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr>', - '<tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr>', - '</table>',]) - result = tabulate(_test_table, _test_table_headers, tablefmt="html") - assert_equal(expected, result) - - -def test_html_headerless(): - "Output: html without headers" - expected = '\n'.join([ - '<table>', - '<tr><td>spam</td><td style="text-align: right;"> 41.9999</td></tr>', - '<tr><td>eggs</td><td style="text-align: right;">451 </td></tr>', - '</table>',]) - result = tabulate(_test_table, tablefmt="html") - assert_equal(expected, result) - - -def test_latex(): - "Output: latex with headers" - result = tabulate(_test_table, _test_table_headers, tablefmt="latex") - expected = "\n".join([r"\begin{tabular}{lr}", - r"\hline", - r" strings & numbers \\", - r"\hline", - r" spam & 41.9999 \\", - r" eggs & 451 \\", - r"\hline", - r"\end{tabular}"]) - assert_equal(expected, result) - - -def test_latex_headerless(): - "Output: latex without headers" - result = tabulate(_test_table, tablefmt="latex") - expected = "\n".join([r"\begin{tabular}{lr}", - r"\hline", - r" spam & 41.9999 \\", - r" eggs & 451 \\", - r"\hline", - r"\end{tabular}"]) - assert_equal(expected, result) - -def test_latex_booktabs(): - "Output: latex with headers, using the booktabs format" - result = tabulate(_test_table, _test_table_headers, tablefmt="latex_booktabs") - expected = "\n".join([r"\begin{tabular}{lr}", - r"\toprule", - r" strings & numbers \\", - r"\midrule", - r" spam & 41.9999 \\", - r" eggs & 451 \\", - r"\bottomrule", - r"\end{tabular}"]) - assert_equal(expected, result) - - -def test_latex_booktabs_headerless(): - "Output: latex without headers, using the booktabs format" - result = tabulate(_test_table, tablefmt="latex_booktabs") - expected = "\n".join([r"\begin{tabular}{lr}", - r"\toprule", - r" spam & 41.9999 \\", - r" eggs & 451 \\", - r"\bottomrule", - r"\end{tabular}"]) - assert_equal(expected, result) - - -def test_no_data(): - "Output: table with no data" - expected = "\n".join(['strings numbers', - '--------- ---------']) - result = tabulate(None, _test_table_headers, tablefmt="simple") - assert_equal(expected, result) - - -def test_empty_data(): - "Output: table with empty data" - expected = "\n".join(['strings numbers', - '--------- ---------']) - result = tabulate([], _test_table_headers, tablefmt="simple") - assert_equal(expected, result) - - -def test_no_data_without_headers(): - "Output: table with no data and no headers" - expected = "\n" - result = tabulate(None, tablefmt="simple") - assert_equal(expected, result) - - -def test_empty_data_without_headers(): - "Output: table with empty data and no headers" - expected = "\n" - result = tabulate([], tablefmt="simple") - assert_equal(expected, result) - - -def test_floatfmt(): - "Output: floating point format" - result = tabulate([['1.23456789'],[1.0]], floatfmt=".3f", tablefmt="plain") - expected = '1.235\n1.000' - assert_equal(expected, result) - - -def test_missingval(): - "Output: substitution of missing values" - result = tabulate([['Alice', 10],['Bob', None]], missingval="n/a", tablefmt="plain") - expected = 'Alice 10\nBob n/a' - assert_equal(expected, result) - - -def test_column_alignment(): - "Output: custom alignment for text and numbers" - expected = '\n'.join(['----- ---', - 'Alice 1', - ' Bob 333', - '----- ---',]) - result = tabulate([['Alice', 1],['Bob', 333]], stralign="right", numalign="center") - assert_equal(expected, result) - - -def test_unaligned_separated(): - "Output: non-aligned data columns" - expected = '\n'.join(['name|score', - 'Alice|1', - 'Bob|333']) - fmt = simple_separated_format("|") - result = tabulate([['Alice', 1],['Bob', 333]], - ["name", "score"], - tablefmt=fmt, stralign=None, numalign=None) - assert_equal(expected, result) - +# -*- coding: utf-8 -*-
+
+"""Test output of the various forms of tabular data."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from tabulate import tabulate, simple_separated_format
+from common import assert_equal, assert_raises, SkipTest
+
+
+# _test_table shows
+# - coercion of a string to a number,
+# - left alignment of text,
+# - decimal point alignment of numbers
+_test_table = [["spam", 41.9999], ["eggs", "451.0"]]
+_test_table_headers = ["strings", "numbers"]
+
+
+def test_plain():
+ "Output: plain with headers"
+ expected = "\n".join(['strings numbers',
+ 'spam 41.9999',
+ 'eggs 451',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="plain")
+ assert_equal(expected, result)
+
+
+def test_plain_headerless():
+ "Output: plain without headers"
+ expected = "\n".join(['spam 41.9999',
+ 'eggs 451',])
+ result = tabulate(_test_table, tablefmt="plain")
+ assert_equal(expected, result)
+
+
+def test_simple():
+ "Output: simple with headers"
+ expected = "\n".join(['strings numbers',
+ '--------- ---------',
+ 'spam 41.9999',
+ 'eggs 451',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_simple_headerless():
+ "Output: simple without headers"
+ expected = "\n".join(['---- --------',
+ 'spam 41.9999',
+ 'eggs 451',
+ '---- --------',])
+ result = tabulate(_test_table, tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_grid():
+ "Output: grid with headers"
+ expected = '\n'.join(['+-----------+-----------+',
+ '| strings | numbers |',
+ '+===========+===========+',
+ '| spam | 41.9999 |',
+ '+-----------+-----------+',
+ '| eggs | 451 |',
+ '+-----------+-----------+',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="grid")
+ assert_equal(expected, result)
+
+
+def test_grid_headerless():
+ "Output: grid without headers"
+ expected = '\n'.join(['+------+----------+',
+ '| spam | 41.9999 |',
+ '+------+----------+',
+ '| eggs | 451 |',
+ '+------+----------+',])
+ result = tabulate(_test_table, tablefmt="grid")
+ assert_equal(expected, result)
+
+
+def test_fancy_grid():
+ "Output: fancy_grid with headers"
+ expected = '\n'.join([
+ '╒═══════════╤═══════════╕',
+ '│ strings │ numbers │',
+ '╞═══════════╪═══════════╡',
+ '│ spam │ 41.9999 │',
+ '├───────────┼───────────┤',
+ '│ eggs │ 451 │',
+ '╘═══════════╧═══════════╛',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="fancy_grid")
+ assert_equal(expected, result)
+
+
+def test_fancy_grid_headerless():
+ "Output: fancy_grid without headers"
+ expected = '\n'.join([
+ '╒══════╤══════════╕',
+ '│ spam │ 41.9999 │',
+ '├──────┼──────────┤',
+ '│ eggs │ 451 │',
+ '╘══════╧══════════╛',])
+ result = tabulate(_test_table, tablefmt="fancy_grid")
+ assert_equal(expected, result)
+
+
+def test_pipe():
+ "Output: pipe with headers"
+ expected = '\n'.join(['| strings | numbers |',
+ '|:----------|----------:|',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="pipe")
+ assert_equal(expected, result)
+
+
+def test_pipe_headerless():
+ "Output: pipe without headers"
+ expected = '\n'.join(['|:-----|---------:|',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',])
+ result = tabulate(_test_table, tablefmt="pipe")
+ assert_equal(expected, result)
+
+
+def test_orgtbl():
+ "Output: orgtbl with headers"
+ expected = '\n'.join(['| strings | numbers |',
+ '|-----------+-----------|',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="orgtbl")
+ assert_equal(expected, result)
+
+
+def test_orgtbl_headerless():
+ "Output: orgtbl without headers"
+ expected = '\n'.join(['| spam | 41.9999 |',
+ '| eggs | 451 |',])
+ result = tabulate(_test_table, tablefmt="orgtbl")
+ assert_equal(expected, result)
+
+def test_psql():
+ "Output: psql with headers"
+ expected = '\n'.join(['+-----------+-----------+',
+ '| strings | numbers |',
+ '|-----------+-----------|',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',
+ '+-----------+-----------+',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="psql")
+ assert_equal(expected, result)
+
+def test_psql_headerless():
+ "Output: psql without headers"
+ expected = '\n'.join(['+------+----------+',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',
+ '+------+----------+',])
+ result = tabulate(_test_table, tablefmt="psql")
+ assert_equal(expected, result)
+
+def test_jira():
+ "Output: jira with headers"
+ expected = '\n'.join(['|| strings || numbers ||',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',])
+
+ result = tabulate(_test_table, _test_table_headers, tablefmt="jira")
+ assert_equal(expected, result)
+
+def test_jira_headerless():
+ "Output: jira without headers"
+ expected = '\n'.join(['| spam | 41.9999 |',
+ '| eggs | 451 |',])
+
+ result = tabulate(_test_table, tablefmt="jira")
+ assert_equal(expected, result)
+
+def test_rst():
+ "Output: rst with headers"
+ expected = '\n'.join(['========= =========',
+ 'strings numbers',
+ '========= =========',
+ 'spam 41.9999',
+ 'eggs 451',
+ '========= =========',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="rst")
+ assert_equal(expected, result)
+
+
+def test_rst_headerless():
+ "Output: rst without headers"
+ expected = '\n'.join(['==== ========',
+ 'spam 41.9999',
+ 'eggs 451',
+ '==== ========',])
+ result = tabulate(_test_table, tablefmt="rst")
+ assert_equal(expected, result)
+
+def test_mediawiki():
+ "Output: mediawiki with headers"
+ expected = '\n'.join(['{| class="wikitable" style="text-align: left;"',
+ '|+ <!-- caption -->',
+ '|-',
+ '! strings !! align="right"| numbers',
+ '|-',
+ '| spam || align="right"| 41.9999',
+ '|-',
+ '| eggs || align="right"| 451',
+ '|}',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="mediawiki")
+ assert_equal(expected, result)
+
+
+def test_mediawiki_headerless():
+ "Output: mediawiki without headers"
+ expected = '\n'.join(['{| class="wikitable" style="text-align: left;"',
+ '|+ <!-- caption -->',
+ '|-',
+ '| spam || align="right"| 41.9999',
+ '|-',
+ '| eggs || align="right"| 451',
+ '|}',])
+ result = tabulate(_test_table, tablefmt="mediawiki")
+ assert_equal(expected, result)
+
+def test_moinmoin():
+ "Output: moinmoin with headers"
+ expected = "\n".join(['|| \'\'\' strings \'\'\' ||<style="text-align: right;"> \'\'\' numbers \'\'\' ||',
+ '|| spam ||<style="text-align: right;"> 41.9999 ||',
+ '|| eggs ||<style="text-align: right;"> 451 ||',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="moinmoin")
+ assert_equal(expected, result)
+
+def test_moinmoin_headerless():
+ "Output: moinmoin without headers"
+ expected = "\n".join(['|| spam ||<style="text-align: right;"> 41.9999 ||',
+ '|| eggs ||<style="text-align: right;"> 451 ||',])
+ result = tabulate (_test_table, tablefmt="moinmoin")
+ assert_equal(expected, result)
+
+
+def test_html():
+ "Output: html with headers"
+ expected = '\n'.join([
+ '<table>',
+ '<thead>',
+ '<tr><th>strings </th><th style="text-align: right;"> numbers</th></tr>',
+ '</thead>',
+ '<tbody>',
+ '<tr><td>spam </td><td style="text-align: right;"> 41.9999</td></tr>',
+ '<tr><td>eggs </td><td style="text-align: right;"> 451 </td></tr>',
+ '</tbody>',
+ '</table>',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="html")
+ assert_equal(expected, result)
+
+
+def test_html_headerless():
+ "Output: html without headers"
+ expected = '\n'.join([
+ '<table>',
+ '<tbody>',
+ '<tr><td>spam</td><td style="text-align: right;"> 41.9999</td></tr>',
+ '<tr><td>eggs</td><td style="text-align: right;">451 </td></tr>',
+ '</tbody>',
+ '</table>',])
+ result = tabulate(_test_table, tablefmt="html")
+ assert_equal(expected, result)
+
+
+def test_latex():
+ "Output: latex with headers"
+ result = tabulate(_test_table, _test_table_headers, tablefmt="latex")
+ expected = "\n".join([r"\begin{tabular}{lr}",
+ r"\hline",
+ r" strings & numbers \\",
+ r"\hline",
+ r" spam & 41.9999 \\",
+ r" eggs & 451 \\",
+ r"\hline",
+ r"\end{tabular}"])
+ assert_equal(expected, result)
+
+
+def test_latex_headerless():
+ "Output: latex without headers"
+ result = tabulate(_test_table, tablefmt="latex")
+ expected = "\n".join([r"\begin{tabular}{lr}",
+ r"\hline",
+ r" spam & 41.9999 \\",
+ r" eggs & 451 \\",
+ r"\hline",
+ r"\end{tabular}"])
+ assert_equal(expected, result)
+
+def test_latex_booktabs():
+ "Output: latex with headers, using the booktabs format"
+ result = tabulate(_test_table, _test_table_headers, tablefmt="latex_booktabs")
+ expected = "\n".join([r"\begin{tabular}{lr}",
+ r"\toprule",
+ r" strings & numbers \\",
+ r"\midrule",
+ r" spam & 41.9999 \\",
+ r" eggs & 451 \\",
+ r"\bottomrule",
+ r"\end{tabular}"])
+ assert_equal(expected, result)
+
+
+def test_latex_booktabs_headerless():
+ "Output: latex without headers, using the booktabs format"
+ result = tabulate(_test_table, tablefmt="latex_booktabs")
+ expected = "\n".join([r"\begin{tabular}{lr}",
+ r"\toprule",
+ r" spam & 41.9999 \\",
+ r" eggs & 451 \\",
+ r"\bottomrule",
+ r"\end{tabular}"])
+ assert_equal(expected, result)
+
+
+def test_textile():
+ "Output: textile without header"
+ result = tabulate(_test_table, tablefmt="textile")
+ expected = """\
+|<. spam |>. 41.9999 |
+|<. eggs |>. 451 |"""
+
+ assert_equal(expected, result)
+
+
+def test_textile_with_header():
+ "Output: textile with header"
+ result = tabulate(_test_table, ['strings', 'numbers'], tablefmt="textile")
+ expected = """\
+|_. strings |_. numbers |
+|<. spam |>. 41.9999 |
+|<. eggs |>. 451 |"""
+
+ assert_equal(expected, result)
+
+
+def test_textile_with_center_align():
+ "Output: textile with center align"
+ result = tabulate(_test_table, tablefmt="textile", stralign='center')
+ expected = """\
+|=. spam |>. 41.9999 |
+|=. eggs |>. 451 |"""
+
+ assert_equal(expected, result)
+
+
+def test_no_data():
+ "Output: table with no data"
+ expected = "\n".join(['strings numbers',
+ '--------- ---------'])
+ result = tabulate(None, _test_table_headers, tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_empty_data():
+ "Output: table with empty data"
+ expected = "\n".join(['strings numbers',
+ '--------- ---------'])
+ result = tabulate([], _test_table_headers, tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_no_data_without_headers():
+ "Output: table with no data and no headers"
+ expected = ""
+ result = tabulate(None, tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_empty_data_without_headers():
+ "Output: table with empty data and no headers"
+ expected = ""
+ result = tabulate([], tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_floatfmt():
+ "Output: floating point format"
+ result = tabulate([['1.23456789'],[1.0]], floatfmt=".3f", tablefmt="plain")
+ expected = '1.235\n1.000'
+ assert_equal(expected, result)
+
+
+def test_missingval():
+ "Output: substitution of missing values"
+ result = tabulate([['Alice', 10],['Bob', None]], missingval="n/a", tablefmt="plain")
+ expected = 'Alice 10\nBob n/a'
+ assert_equal(expected, result)
+
+
+def test_column_alignment():
+ "Output: custom alignment for text and numbers"
+ expected = '\n'.join(['----- ---',
+ 'Alice 1',
+ ' Bob 333',
+ '----- ---',])
+ result = tabulate([['Alice', 1],['Bob', 333]], stralign="right", numalign="center")
+ assert_equal(expected, result)
+
+
+def test_unaligned_separated():
+ "Output: non-aligned data columns"
+ expected = '\n'.join(['name|score',
+ 'Alice|1',
+ 'Bob|333'])
+ fmt = simple_separated_format("|")
+ result = tabulate([['Alice', 1],['Bob', 333]],
+ ["name", "score"],
+ tablefmt=fmt, stralign=None, numalign=None)
+ assert_equal(expected, result)
+
+
+def test_pandas_with_index():
+ "Output: a pandas Dataframe with an index"
+ try:
+ import pandas
+ df = pandas.DataFrame([["one",1],["two",None]],
+ columns=["string","number"],
+ index=["a","b"])
+ expected = "\n".join(
+ [' string number',
+ '-- -------- --------',
+ 'a one 1',
+ 'b two nan'])
+ result = tabulate(df, headers="keys")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas_with_index is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_pandas_without_index():
+ "Output: a pandas Dataframe without an index"
+ try:
+ import pandas
+ df = pandas.DataFrame([["one",1],["two",None]],
+ columns=["string","number"],
+ index=["a","b"])
+ expected = "\n".join(
+ ['string number',
+ '-------- --------',
+ 'one 1',
+ 'two nan'])
+ result = tabulate(df, headers="keys", showindex=False)
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas_without_index is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_pandas_rst_with_index():
+ "Output: a pandas Dataframe with an index in ReStructuredText format"
+ try:
+ import pandas
+ df = pandas.DataFrame([["one", 1], ["two", None]],
+ columns=["string", "number"],
+ index=["a", "b"])
+ expected = "\n".join(
+ ['==== ======== ========',
+ '.. string number',
+ '==== ======== ========',
+ 'a one 1',
+ 'b two nan',
+ '==== ======== ========'])
+ result = tabulate(df, tablefmt="rst", headers="keys")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas_rst_with_index is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_pandas_rst_with_named_index():
+ "Output: a pandas Dataframe with a named index in ReStructuredText format"
+ try:
+ import pandas
+ index = pandas.Index(["a", "b"], name='index')
+ df = pandas.DataFrame([["one", 1], ["two", None]],
+ columns=["string", "number"],
+ index=index)
+ expected = "\n".join(
+ ['======= ======== ========',
+ 'index string number',
+ '======= ======== ========',
+ 'a one 1',
+ 'b two nan',
+ '======= ======== ========'])
+ result = tabulate(df, tablefmt="rst", headers="keys")
+ assert_equal(expected, result)
+ except ImportError:
+ print("test_pandas_rst_with_index is skipped")
+ raise SkipTest() # this test is optional
+
+
+def test_dict_like_with_index():
+ "Output: a table with a running index"
+ dd = {"b": range(101,104)}
+ expected = "\n".join([
+ ' b',
+ '-- ---',
+ ' 0 101',
+ ' 1 102',
+ ' 2 103'])
+ result = tabulate(dd, "keys", showindex=True)
+ assert_equal(result, expected)
+
+
+def test_list_of_lists_with_index():
+ "Output: a table with a running index"
+ dd = zip(*[range(3), range(101,104)])
+ # keys' order (hence columns' order) is not deterministic in Python 3
+ # => we have to consider both possible results as valid
+ expected = "\n".join([
+ ' a b',
+ '-- --- ---',
+ ' 0 0 101',
+ ' 1 1 102',
+ ' 2 2 103'])
+ result = tabulate(dd, headers=["a","b"], showindex=True)
+ assert_equal(result, expected)
+
+def test_list_of_lists_with_supplied_index():
+ "Output: a table with a supplied index"
+ dd = zip(*[list(range(3)), list(range(101,104))])
+ expected = "\n".join([
+ ' a b',
+ '-- --- ---',
+ ' 1 0 101',
+ ' 2 1 102',
+ ' 3 2 103'])
+ result = tabulate(dd, headers=["a","b"], showindex=[1,2,3])
+ assert_equal(result, expected)
+ # TODO: make it a separate test case
+ # the index must be as long as the number of rows
+ assert_raises(ValueError, lambda: tabulate(dd, headers=["a","b"], showindex=[1,2]))
+
+
+def test_list_of_lists_with_index_firstrow():
+ "Output: a table with a running index and header='firstrow'"
+ dd = zip(*[["a"]+list(range(3)), ["b"]+list(range(101,104))])
+ expected = "\n".join([
+ ' a b',
+ '-- --- ---',
+ ' 0 0 101',
+ ' 1 1 102',
+ ' 2 2 103'])
+ result = tabulate(dd, headers="firstrow", showindex=True)
+ assert_equal(result, expected)
+ # TODO: make it a separate test case
+ # the index must be as long as the number of rows
+ assert_raises(ValueError, lambda: tabulate(dd, headers="firstrow", showindex=[1,2]))
+
+
+def test_disable_numparse_default():
+ "Output: Default table output with number parsing and alignment"
+ expected = "\n".join(['strings numbers',
+ '--------- ---------',
+ 'spam 41.9999',
+ 'eggs 451',])
+ result = tabulate(_test_table, _test_table_headers)
+ assert_equal(expected, result)
+ result = tabulate(_test_table, _test_table_headers, disable_numparse=False)
+ assert_equal(expected, result)
+
+def test_disable_numparse_true():
+ "Output: Default table output, but without number parsing and alignment"
+ expected = "\n".join(['strings numbers',
+ '--------- ---------',
+ 'spam 41.9999',
+ 'eggs 451.0',])
+ result = tabulate(_test_table, _test_table_headers, disable_numparse=True)
+ assert_equal(expected, result)
+
+def test_disable_numparse_list():
+ "Output: Default table output, but with number parsing selectively disabled"
+ table_headers = ['h1', 'h2', 'h3']
+ test_table = [['foo', 'bar', '42992e1']]
+ expected = "\n".join(['h1 h2 h3',
+ '---- ---- -------',
+ 'foo bar 42992e1',])
+ result = tabulate(test_table, table_headers, disable_numparse=[2])
+ assert_equal(expected, result)
+
+ expected = "\n".join(['h1 h2 h3',
+ '---- ---- ------',
+ 'foo bar 429920',])
+ result = tabulate(test_table, table_headers, disable_numparse=[0, 1])
+ assert_equal(expected, result)
diff --git a/test/test_regression.py b/test/test_regression.py index 6d56bee..af22734 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -1,233 +1,333 @@ -# -*- coding: utf-8 -*- - -"""Regression tests.""" - -from __future__ import print_function -from __future__ import unicode_literals -from tabulate import tabulate, _text_type -from common import assert_equal, assert_in - - -def test_ansi_color_in_table_cells(): - "Regression: ANSI color in table cells (issue #5)." - colortable = [('test', '\x1b[31mtest\x1b[0m', '\x1b[32mtest\x1b[0m')] - colorlessheaders = ('test', 'test', 'test') - formatted = tabulate(colortable, colorlessheaders, 'pipe') - expected = "\n".join(['| test | test | test |', - '|:-------|:-------|:-------|', - '| test | \x1b[31mtest\x1b[0m | \x1b[32mtest\x1b[0m |']) - print("expected: %r\n\ngot: %r\n" % (expected, formatted)) - assert_equal(expected, formatted) - - -def test_alignment_of_colored_cells(): - "Regression: Align ANSI-colored values as if they were colorless." - colortable = [('test', 42, '\x1b[31m42\x1b[0m'), ('test', 101, '\x1b[32m101\x1b[0m')] - colorheaders = ('test', '\x1b[34mtest\x1b[0m', 'test') - formatted = tabulate(colortable, colorheaders, 'grid') - expected = '\n'.join(['+--------+--------+--------+', - '| test | \x1b[34mtest\x1b[0m | test |', - '+========+========+========+', - '| test | 42 | \x1b[31m42\x1b[0m |', - '+--------+--------+--------+', - '| test | 101 | \x1b[32m101\x1b[0m |', - '+--------+--------+--------+']) - print("expected: %r\n\ngot: %r\n" % (expected, formatted)) - assert_equal(expected, formatted) - - -def test_iter_of_iters_with_headers(): - "Regression: Generator of generators with a generator of headers (issue #9)." - - def mk_iter_of_iters(): - def mk_iter(): - for i in range(3): - yield i - for r in range(3): - yield mk_iter() - - def mk_headers(): - for h in ["a", "b", "c"]: - yield h - - formatted = tabulate(mk_iter_of_iters(), headers=mk_headers()) - expected = '\n'.join([' a b c', - '--- --- ---', - ' 0 1 2', - ' 0 1 2', - ' 0 1 2']) - print("expected: %r\n\ngot: %r\n" % (expected, formatted)) - assert_equal(expected, formatted) - - -def test_datetime_values(): - "Regression: datetime, date, and time values in cells (issue #10)." - import datetime - dt = datetime.datetime(1991,2,19,17,35,26) - d = datetime.date(1991,2,19) - t = datetime.time(17,35,26) - formatted = tabulate([[dt, d, t]]) - expected = '\n'.join(['------------------- ---------- --------', - '1991-02-19 17:35:26 1991-02-19 17:35:26', - '------------------- ---------- --------']) - print("expected: %r\n\ngot: %r\n" % (expected, formatted)) - assert_equal(expected, formatted) - - -def test_simple_separated_format(): - "Regression: simple_separated_format() accepts any separator (issue #12)" - from tabulate import simple_separated_format - fmt = simple_separated_format("!") - expected = 'spam!eggs' - formatted = tabulate([["spam", "eggs"]], tablefmt=fmt) - print("expected: %r\n\ngot: %r\n" % (expected, formatted)) - assert_equal(expected, formatted) - - -def py3test_require_py3(): - "Regression: py33 tests should actually use Python 3 (issue #13)" - from platform import python_version_tuple - print("Expected Python version: 3.x.x") - print("Python version used for tests: %s.%s.%s" % python_version_tuple()) - assert_equal(python_version_tuple()[0], '3') - - -def test_simple_separated_format_with_headers(): - "Regression: simple_separated_format() on tables with headers (issue #15)" - from tabulate import simple_separated_format - expected = ' a| b\n 1| 2' - formatted = tabulate([[1,2]], headers=["a", "b"], tablefmt=simple_separated_format("|")) - assert_equal(expected, formatted) - - -def test_column_type_of_bytestring_columns(): - "Regression: column type for columns of bytestrings (issue #16)" - from tabulate import _column_type, _binary_type - result = _column_type([b"foo", b"bar"]) - expected = _binary_type - assert_equal(result, expected) - - -def test_numeric_column_headers(): - "Regression: numbers as column headers (issue #22)" - result = tabulate([[1],[2]], [42]) - expected = ' 42\n----\n 1\n 2' - assert_equal(result, expected) - - lod = [dict((p,i) for p in range(5)) for i in range(5)] - result = tabulate(lod, "keys") - expected = "\n".join([ - " 0 1 2 3 4", - "--- --- --- --- ---", - " 0 0 0 0 0", - " 1 1 1 1 1", - " 2 2 2 2 2", - " 3 3 3 3 3", - " 4 4 4 4 4",]) - assert_equal(result, expected) - - -def test_88_256_ANSI_color_codes(): - "Regression: color codes for terminals with 88/256 colors (issue #26)" - colortable = [('\x1b[48;5;196mred\x1b[49m', - '\x1b[38;5;196mred\x1b[39m')] - colorlessheaders = ('background', 'foreground') - formatted = tabulate(colortable, colorlessheaders, 'pipe') - expected = "\n".join([ - '| background | foreground |', - '|:-------------|:-------------|', - '| \x1b[48;5;196mred\x1b[49m | \x1b[38;5;196mred\x1b[39m |']) - print("expected: %r\n\ngot: %r\n" % (expected, formatted)) - assert_equal(expected, formatted) - - -def test_column_with_mixed_value_types(): - "Regression: mixed value types in the same column (issue #31)" - expected = '\n'.join([ - '-----', - '', - 'a', - 'я', - '0', - 'False', - '-----', - ]) - data = [[None], ['a'], ['\u044f'], [0], [False]] - table = tabulate(data) - assert_equal(table, expected) - - -def test_latex_escape_special_chars(): - "Regression: escape special characters in LaTeX output (issue #32)" - expected = "\n".join([ - r'\begin{tabular}{l}', - r'\hline', - r' foo\^{}bar \\', - r'\hline', - r' \&\%\^{}\_\$\#\{\}\ensuremath{<}\ensuremath{>}\textasciitilde{} \\', - r'\hline', - r'\end{tabular}']) - result = tabulate([["&%^_$#{}<>~"]], ["foo^bar"], tablefmt="latex") - assert_equal(result, expected) - - -def test_isconvertible_on_set_values(): - "Regression: don't fail with TypeError on set values (issue #35)" - expected_py2 = "\n".join([ - 'a b', - '--- -------', - 'Foo set([])',]) - expected_py3 = "\n".join([ - 'a b', - '--- -----', - 'Foo set()',]) - result = tabulate([["Foo",set()]], headers=["a","b"]) - assert_in(result, [expected_py2, expected_py3]) - - -def test_ansi_color_for_decimal_numbers(): - "Regression: ANSI colors for decimal numbers (issue #36)" - table = [["Magenta", "\033[95m" + "1.1" + "\033[0m"]] - expected = "\n".join([ - '------- ---', - 'Magenta \x1b[95m1.1\x1b[0m', - '------- ---']) - result = tabulate(table) - assert_equal(result, expected) - - -def test_alignment_of_decimal_numbers_with_ansi_color(): - "Regression: alignment for decimal numbers with ANSI color (issue #42)" - v1 = "\033[95m" + "12.34" + "\033[0m" - v2 = "\033[95m" + "1.23456" + "\033[0m" - table = [[v1], [v2]] - expected = "\n".join([ - '\x1b[95m12.34\x1b[0m', - ' \x1b[95m1.23456\x1b[0m']) - result = tabulate(table, tablefmt="plain") - assert_equal(result, expected) - - -def test_long_integers(): - "Regression: long integers should be printed as integers (issue #48)" - table = [[18446744073709551614]] - result = tabulate(table, tablefmt="plain") - expected = "18446744073709551614" - assert_equal(result, expected) - - -def test_colorclass_colors(): - "Regression: ANSI colors in a unicode/str subclass (issue #49)" - try: - import colorclass - s = colorclass.Color("{magenta}3.14{/magenta}") - result = tabulate([[s]], tablefmt="plain") - expected = "\x1b[35m3.14\x1b[39m" - assert_equal(result, expected) - except ImportError: - class textclass(_text_type): - pass - s = textclass("\x1b[35m3.14\x1b[39m") - result = tabulate([[s]], tablefmt="plain") - expected = "\x1b[35m3.14\x1b[39m" - assert_equal(result, expected) +# -*- coding: utf-8 -*-
+
+"""Regression tests."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+from tabulate import tabulate, _text_type, _long_type
+from common import assert_equal, assert_in, SkipTest
+
+
+def test_ansi_color_in_table_cells():
+ "Regression: ANSI color in table cells (issue #5)."
+ colortable = [('test', '\x1b[31mtest\x1b[0m', '\x1b[32mtest\x1b[0m')]
+ colorlessheaders = ('test', 'test', 'test')
+ formatted = tabulate(colortable, colorlessheaders, 'pipe')
+ expected = "\n".join(['| test | test | test |',
+ '|:-------|:-------|:-------|',
+ '| test | \x1b[31mtest\x1b[0m | \x1b[32mtest\x1b[0m |'])
+ print("expected: %r\n\ngot: %r\n" % (expected, formatted))
+ assert_equal(expected, formatted)
+
+
+def test_alignment_of_colored_cells():
+ "Regression: Align ANSI-colored values as if they were colorless."
+ colortable = [('test', 42, '\x1b[31m42\x1b[0m'), ('test', 101, '\x1b[32m101\x1b[0m')]
+ colorheaders = ('test', '\x1b[34mtest\x1b[0m', 'test')
+ formatted = tabulate(colortable, colorheaders, 'grid')
+ expected = '\n'.join(['+--------+--------+--------+',
+ '| test | \x1b[34mtest\x1b[0m | test |',
+ '+========+========+========+',
+ '| test | 42 | \x1b[31m42\x1b[0m |',
+ '+--------+--------+--------+',
+ '| test | 101 | \x1b[32m101\x1b[0m |',
+ '+--------+--------+--------+'])
+ print("expected: %r\n\ngot: %r\n" % (expected, formatted))
+ assert_equal(expected, formatted)
+
+
+def test_iter_of_iters_with_headers():
+ "Regression: Generator of generators with a gen. of headers (issue #9)."
+
+ def mk_iter_of_iters():
+ def mk_iter():
+ for i in range(3):
+ yield i
+ for r in range(3):
+ yield mk_iter()
+
+ def mk_headers():
+ for h in ["a", "b", "c"]:
+ yield h
+
+ formatted = tabulate(mk_iter_of_iters(), headers=mk_headers())
+ expected = '\n'.join([' a b c',
+ '--- --- ---',
+ ' 0 1 2',
+ ' 0 1 2',
+ ' 0 1 2'])
+ print("expected: %r\n\ngot: %r\n" % (expected, formatted))
+ assert_equal(expected, formatted)
+
+
+def test_datetime_values():
+ "Regression: datetime, date, and time values in cells (issue #10)."
+ import datetime
+ dt = datetime.datetime(1991,2,19,17,35,26)
+ d = datetime.date(1991,2,19)
+ t = datetime.time(17,35,26)
+ formatted = tabulate([[dt, d, t]])
+ expected = '\n'.join(['------------------- ---------- --------',
+ '1991-02-19 17:35:26 1991-02-19 17:35:26',
+ '------------------- ---------- --------'])
+ print("expected: %r\n\ngot: %r\n" % (expected, formatted))
+ assert_equal(expected, formatted)
+
+
+def test_simple_separated_format():
+ "Regression: simple_separated_format() accepts any separator (issue #12)"
+ from tabulate import simple_separated_format
+ fmt = simple_separated_format("!")
+ expected = 'spam!eggs'
+ formatted = tabulate([["spam", "eggs"]], tablefmt=fmt)
+ print("expected: %r\n\ngot: %r\n" % (expected, formatted))
+ assert_equal(expected, formatted)
+
+
+def py3test_require_py3():
+ "Regression: py33 tests should actually use Python 3 (issue #13)"
+ from platform import python_version_tuple
+ print("Expected Python version: 3.x.x")
+ print("Python version used for tests: %s.%s.%s" % python_version_tuple())
+ assert_equal(python_version_tuple()[0], '3')
+
+
+def test_simple_separated_format_with_headers():
+ "Regression: simple_separated_format() on tables with headers (issue #15)"
+ from tabulate import simple_separated_format
+ expected = ' a| b\n 1| 2'
+ formatted = tabulate([[1,2]], headers=["a", "b"], tablefmt=simple_separated_format("|"))
+ assert_equal(expected, formatted)
+
+
+def test_column_type_of_bytestring_columns():
+ "Regression: column type for columns of bytestrings (issue #16)"
+ from tabulate import _column_type, _binary_type
+ result = _column_type([b"foo", b"bar"])
+ expected = _binary_type
+ assert_equal(result, expected)
+
+
+def test_numeric_column_headers():
+ "Regression: numbers as column headers (issue #22)"
+ result = tabulate([[1],[2]], [42])
+ expected = ' 42\n----\n 1\n 2'
+ assert_equal(result, expected)
+
+ lod = [dict((p,i) for p in range(5)) for i in range(5)]
+ result = tabulate(lod, "keys")
+ expected = "\n".join([
+ " 0 1 2 3 4",
+ "--- --- --- --- ---",
+ " 0 0 0 0 0",
+ " 1 1 1 1 1",
+ " 2 2 2 2 2",
+ " 3 3 3 3 3",
+ " 4 4 4 4 4",])
+ assert_equal(result, expected)
+
+
+def test_88_256_ANSI_color_codes():
+ "Regression: color codes for terminals with 88/256 colors (issue #26)"
+ colortable = [('\x1b[48;5;196mred\x1b[49m',
+ '\x1b[38;5;196mred\x1b[39m')]
+ colorlessheaders = ('background', 'foreground')
+ formatted = tabulate(colortable, colorlessheaders, 'pipe')
+ expected = "\n".join([
+ '| background | foreground |',
+ '|:-------------|:-------------|',
+ '| \x1b[48;5;196mred\x1b[49m | \x1b[38;5;196mred\x1b[39m |'])
+ print("expected: %r\n\ngot: %r\n" % (expected, formatted))
+ assert_equal(expected, formatted)
+
+
+def test_column_with_mixed_value_types():
+ "Regression: mixed value types in the same column (issue #31)"
+ expected = '\n'.join([
+ '-----',
+ '',
+ 'a',
+ 'я',
+ '0',
+ 'False',
+ '-----',
+ ])
+ data = [[None], ['a'], ['\u044f'], [0], [False]]
+ table = tabulate(data)
+ assert_equal(table, expected)
+
+
+def test_latex_escape_special_chars():
+ "Regression: escape special characters in LaTeX output (issue #32)"
+ expected = "\n".join([
+ r'\begin{tabular}{l}',
+ r'\hline',
+ r' foo\^{}bar \\',
+ r'\hline',
+ r' \&\%\^{}\_\$\#\{\}\ensuremath{<}\ensuremath{>}\textasciitilde{} \\',
+ r'\hline',
+ r'\end{tabular}'])
+ result = tabulate([["&%^_$#{}<>~"]], ["foo^bar"], tablefmt="latex")
+ assert_equal(result, expected)
+
+
+def test_isconvertible_on_set_values():
+ "Regression: don't fail with TypeError on set values (issue #35)"
+ expected_py2 = "\n".join([
+ 'a b',
+ '--- -------',
+ 'Foo set([])',])
+ expected_py3 = "\n".join([
+ 'a b',
+ '--- -----',
+ 'Foo set()',])
+ result = tabulate([["Foo",set()]], headers=["a","b"])
+ assert_in(result, [expected_py2, expected_py3])
+
+
+def test_ansi_color_for_decimal_numbers():
+ "Regression: ANSI colors for decimal numbers (issue #36)"
+ table = [["Magenta", "\033[95m" + "1.1" + "\033[0m"]]
+ expected = "\n".join([
+ '------- ---',
+ 'Magenta \x1b[95m1.1\x1b[0m',
+ '------- ---'])
+ result = tabulate(table)
+ assert_equal(result, expected)
+
+
+def test_alignment_of_decimal_numbers_with_ansi_color():
+ "Regression: alignment for decimal numbers with ANSI color (issue #42)"
+ v1 = "\033[95m" + "12.34" + "\033[0m"
+ v2 = "\033[95m" + "1.23456" + "\033[0m"
+ table = [[v1], [v2]]
+ expected = "\n".join([
+ '\x1b[95m12.34\x1b[0m',
+ ' \x1b[95m1.23456\x1b[0m'])
+ result = tabulate(table, tablefmt="plain")
+ assert_equal(result, expected)
+
+
+def test_long_integers():
+ "Regression: long integers should be printed as integers (issue #48)"
+ table = [[18446744073709551614]]
+ result = tabulate(table, tablefmt="plain")
+ expected = "18446744073709551614"
+ assert_equal(result, expected)
+
+
+def test_colorclass_colors():
+ "Regression: ANSI colors in a unicode/str subclass (issue #49)"
+ try:
+ import colorclass
+ s = colorclass.Color("{magenta}3.14{/magenta}")
+ result = tabulate([[s]], tablefmt="plain")
+ expected = "\x1b[35m3.14\x1b[39m"
+ assert_equal(result, expected)
+ except ImportError:
+ class textclass(_text_type):
+ pass
+ s = textclass("\x1b[35m3.14\x1b[39m")
+ result = tabulate([[s]], tablefmt="plain")
+ expected = "\x1b[35m3.14\x1b[39m"
+ assert_equal(result, expected)
+
+
+def test_mix_normal_and_wide_characters():
+ "Regression: wide characters in a grid format (issue #51)"
+ try:
+ import wcwidth
+ ru_text = '\u043f\u0440\u0438\u0432\u0435\u0442'
+ cn_text = '\u4f60\u597d'
+ result = tabulate([[ru_text], [cn_text]], tablefmt="grid")
+ expected = "\n".join([
+ '+--------+',
+ '| \u043f\u0440\u0438\u0432\u0435\u0442 |',
+ '+--------+',
+ '| \u4f60\u597d |',
+ '+--------+'])
+ assert_equal(result, expected)
+ except ImportError:
+ print("test_mix_normal_and_wide_characters is skipped (requires wcwidth lib)")
+ raise SkipTest()
+
+
+def test_align_long_integers():
+ "Regression: long integers should be aligned as integers (issue #61)"
+ table = [[_long_type(1)], [_long_type(234)]]
+ result = tabulate(table, tablefmt="plain")
+ expected = "\n".join([" 1",
+ "234"])
+ assert_equal(result, expected)
+
+
+def test_numpy_array_as_headers():
+ "Regression: NumPy array used as headers (issue #62)"
+ try:
+ import numpy as np
+ headers = np.array(["foo", "bar"])
+ result = tabulate([], headers, tablefmt="plain")
+ expected = "foo bar"
+ assert_equal(result, expected)
+ except ImportError:
+ raise SkipTest()
+
+
+def test_boolean_columns():
+ "Regression: recognize boolean columns (issue #64)"
+ xortable = [[False, True], [True, False]]
+ expected = "\n".join(["False True",
+ "True False"])
+ result = tabulate(xortable, tablefmt="plain")
+ assert_equal(result, expected)
+
+
+def test_ansi_color_bold_and_fgcolor():
+ "Regression: set ANSI color and bold face together (issue #65)"
+ table = [["1", "2", "3"], ["4", "\x1b[1;31m5\x1b[1;m", "6"], ["7", "8", "9"]]
+ result = tabulate(table, tablefmt="grid")
+ expected = "\n".join([
+ u'+---+---+---+',
+ u'| 1 | 2 | 3 |',
+ u'+---+---+---+',
+ u'| 4 | \x1b[1;31m5\x1b[1;m | 6 |',
+ u'+---+---+---+',
+ u'| 7 | 8 | 9 |',
+ u'+---+---+---+'])
+ assert_equal(result, expected)
+
+
+def test_empty_table_with_keys_as_header():
+ "Regression: headers='keys' on an empty table (issue #81)"
+ result = tabulate([], headers="keys")
+ expected = ""
+ assert_equal(result, expected)
+
+
+def test_escape_empty_cell_in_first_column_in_rst():
+ "Regression: escape empty cells of the first column in RST format (issue #82)"
+ table = [["foo", 1], ["", 2], ["bar", 3]]
+ headers = ["", "val"]
+ expected = "\n".join([
+ u"==== =====",
+ u".. val",
+ u"==== =====",
+ u"foo 1",
+ u".. 2",
+ u"bar 3",
+ u"==== ====="])
+ result = tabulate(table, headers, tablefmt="rst")
+ assert_equal(result, expected)
+
+
+def test_ragged_rows():
+ "Regression: allow rows with different number of columns (issue #85)"
+ table = [[1,2,3], [1,2], [1,2,3,4]]
+ expected = "\n".join([
+ u"- - - -",
+ u"1 2 3",
+ u"1 2",
+ u"1 2 3 4",
+ u"- - - -"])
+ result = tabulate(table)
+ assert_equal(result, expected)
diff --git a/tox.ini b/tox.ini deleted file mode 100644 index f511ef9..0000000 --- a/tox.ini +++ /dev/null @@ -1,79 +0,0 @@ -# Tox (http://tox.testrun.org/) is a tool for running tests -# in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. -# -# To run tests against numpy and pandas, run "tox -e py27-extra,py33-extra" -# from this directory. This will create a much bigger virtual environments -# for testing and it is disabled by default. - -[tox] -envlist = py26, py27, py32, py33, py34 - -[testenv] -commands = nosetests -v --with-doctest test/ tabulate.py -deps = - nose - funcsigs - - -[testenv:py26] -basepython = python2.6 -commands = nosetests -v -e 'py27orlater' --with-doctest test/ tabulate.py -deps = - nose - funcsigs - - -[testenv:py27-extra] -basepython = python2.7 -commands = nosetests -v --with-doctest test/ tabulate.py -deps = - nose - funcsigs - numpy - pandas - - -[testenv:py32] -basepython = python3.2 -commands = nosetests -v --with-doctest -i 'py3test_*' test/ tabulate.py -deps = - nose - funcsigs - - -[testenv:py33] -basepython = python3.3 -commands = nosetests -v --with-doctest -i 'py3test_*' test/ tabulate.py -deps = - nose - funcsigs - - -[testenv:py33-extra] -basepython = python3.3 -commands = nosetests -v --with-doctest -i 'py3test_*' test/ tabulate.py -deps = - nose - funcsigs - numpy - pandas - - -[testenv:py34] -basepython = python3.4 -commands = nosetests -v --with-doctest -i 'py3test_*' test/ tabulate.py -deps = - nose - funcsigs - - -[testenv:py34-extra] -basepython = python3.4 -commands = nosetests -v --with-doctest -i 'py3test_*' test/ tabulate.py -deps = - nose - funcsigs - numpy - pandas |
