summaryrefslogtreecommitdiff
diff options
authorSandro Tosi <morph@debian.org>2016-12-16 19:37:17 -0500
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2016-12-17 04:20:57 +0000
commitf68be773822d9dde393354c2c43a5bdf5ea2caef (patch)
treea70af822ea77a0993d352630dd992e670595f4c3
parent7054a003ef9a0c8128cf2fe2c9d1db7ff45f73c5 (diff)
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--.gitignore5
-rw-r--r--HOWTOPUBLISH6
-rw-r--r--LICENSE40
-rw-r--r--MANIFEST.in6
-rw-r--r--PKG-INFO590
-rw-r--r--[l---------]README0
-rw-r--r--README.rst1043
-rw-r--r--benchmark.py97
-rw-r--r--debian/.git-dpm11
-rw-r--r--debian/changelog22
-rw-r--r--debian/compat2
-rw-r--r--debian/control12
-rw-r--r--debian/copyright5
-rw-r--r--debian/python-tabulate.examples2
-rwxr-xr-xdebian/rules3
-rw-r--r--setup.cfg5
-rw-r--r--setup.py118
-rw-r--r--tabulate.egg-info/.PKG-INFO.swpbin0 -> 16384 bytes
-rw-r--r--tabulate.egg-info/PKG-INFO590
-rw-r--r--tabulate.egg-info/SOURCES.txt18
-rw-r--r--tabulate.egg-info/dependency_links.txt1
-rw-r--r--tabulate.egg-info/entry_points.txt3
-rw-r--r--tabulate.egg-info/requires.txt3
-rw-r--r--tabulate.egg-info/top_level.txt1
-rw-r--r--tabulate.py2420
-rw-r--r--test/common.py31
-rw-r--r--test/test_api.py112
-rw-r--r--test/test_cli.py380
-rw-r--r--test/test_input.py853
-rw-r--r--test/test_output.py943
-rw-r--r--test/test_regression.py566
-rw-r--r--tox.ini79
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
diff --git a/LICENSE b/LICENSE
index f473ca2..20cea53 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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
diff --git a/README b/README
index facdd59..facdd59 120000..100644
--- a/README
+++ b/README
diff --git a/README.rst b/README.rst
index ee2f3a6..5c26236 100644
--- a/README.rst
+++ b/README.rst
@@ -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
+
diff --git a/setup.py b/setup.py
index 0d7a2ae..ff84457 100644
--- a/setup.py
+++ b/setup.py
@@ -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
new file mode 100644
index 0000000..f01081e
--- /dev/null
+++ b/tabulate.egg-info/.PKG-INFO.swp
Binary files differ
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