diff options
| -rw-r--r-- | LICENSE | 2 | ||||
| -rw-r--r-- | MANIFEST.in | 2 | ||||
| -rw-r--r-- | PKG-INFO | 257 | ||||
| -rw-r--r-- | README.rst | 233 | ||||
| -rw-r--r-- | benchmark.py | 118 | ||||
| -rw-r--r-- | debian/changelog | 7 | ||||
| -rw-r--r-- | debian/control | 2 | ||||
| -rw-r--r-- | setup.cfg | 1 | ||||
| -rw-r--r-- | setup.py | 14 | ||||
| -rw-r--r-- | tabulate.egg-info/.PKG-INFO.swp | bin | 16384 -> 0 bytes | |||
| -rw-r--r-- | tabulate.egg-info/PKG-INFO | 257 | ||||
| -rw-r--r-- | tabulate.egg-info/SOURCES.txt | 4 | ||||
| -rw-r--r-- | tabulate.py | 333 | ||||
| -rw-r--r-- | test/common.py | 46 | ||||
| -rw-r--r-- | test/test_api.py | 3 | ||||
| -rw-r--r-- | test/test_internal.py | 50 | ||||
| -rw-r--r-- | test/test_output.py | 612 |
17 files changed, 1741 insertions, 200 deletions
@@ -1,4 +1,4 @@ -Copyright (c) 2011-2016 Sergey Astanin
+Copyright (c) 2011-2017 Sergey Astanin
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/MANIFEST.in b/MANIFEST.in index 63a18f5..bb21d49 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ include LICENSE
include README
include README.rst
+include test/common.py
+include benchmark.py
@@ -1,31 +1,11 @@ Metadata-Version: 1.1
Name: tabulate
-Version: 0.7.7
+Version: 0.8.2
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.
-
+License: MIT
Description: ===============
python-tabulate
===============
@@ -175,12 +155,15 @@ Description: =============== - "pipe"
- "orgtbl"
- "jira"
+ - "presto"
- "psql"
- "rst"
- "mediawiki"
- "moinmoin"
+ - "youtrack"
- "html"
- "latex"
+ - "latex_raw"
- "latex_booktabs"
- "textile"
@@ -233,6 +216,15 @@ Description: =============== │ bacon │ 0 │
╘════════╧═══════╛
+ ``presto`` is like tables formatted by Presto cli::
+
+ >>> print tabulate.tabulate()
+ item | qty
+ --------+-------
+ spam | 42
+ eggs | 451
+ bacon | 0
+
``psql`` is like tables formatted by Postgres' psql cli::
>>> print tabulate.tabulate()
@@ -309,6 +301,15 @@ Description: =============== || eggs || 451 ||
|| bacon || ||
+ ``youtrack`` format produces a table markup used in Youtrack
+ tickets::
+
+ >>> print tabulate(d,headers,tablefmt="youtrack")
+ || item || quantity ||
+ | spam | 41.999 |
+ | eggs | 451 |
+ | bacon | |
+
``textile`` format produces a table markup used in `Textile` format::
>>> print tabulate(table, headers, tablefmt='textile')
@@ -329,7 +330,9 @@ Description: =============== </tbody>
</table>
- ``latex`` format creates a ``tabular`` environment for LaTeX markup::
+ ``latex`` format creates a ``tabular`` environment for LaTeX markup,
+ replacing special characters like ```` or ``\`` to their LaTeX
+ correspondents::
>>> print tabulate(table, headers, tablefmt="latex")
\begin{tabular}{lr}
@@ -342,6 +345,9 @@ Description: =============== \hline
\end{tabular}
+ ``latex_raw`` behaves like ``latex`` but does not escape LaTeX commands
+ and special characters.
+
``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup
using spacing and style from the ``booktabs`` package.
@@ -353,6 +359,7 @@ Description: =============== .. _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
+ .. _MoinMoin: https://moinmo.in/
Column alignment
@@ -416,13 +423,33 @@ Description: =============== ``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
-- ------
+ ``floatfmt`` argument can be a list or a tuple of format strings,
+ one per column, in which case every column may have different number formatting::
+
+ >>> print tabulate([[0.12345, 0.12345, 0.12345]], floatfmt=(".1f", ".3f"))
+ --- ----- -------
+ 0.1 0.123 0.12345
+ --- ----- -------
+
+
+
+ Text formatting
+ ~~~~~~~~~~~~~~~
+
+ By default, ``tabulate`` removes leading and trailing whitespace from text
+ columns. To disable whitespace removal, set the global module-level flag
+ ``PRESERVE_WHITESPACE``::
+
+ import tabulate
+ tabulate.PRESERVE_WHITESPACE = True
+
+
Wide (fullwidth CJK) symbols
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -441,6 +468,141 @@ Description: =============== tabulate.WIDE_CHARS_MODE = False
+ Multiline cells
+ ~~~~~~~~~~~~~~~
+
+ Most table formats support multiline cell text (text containing newline
+ characters). The newline characters are honored as line break characters.
+
+ Multiline cells are supported for data rows and for header rows.
+
+ Further automatic line breaks are not inserted. Of course, some output formats
+ such as latex or html handle automatic formatting of the cell content on their
+ own, but for those that don't, the newline characters in the input cell text
+ are the only means to break a line in cell text.
+
+ Note that some output formats (e.g. simple, or plain) do not represent row
+ delimiters, so that the representation of multiline cells in such formats
+ may be ambiguous to the reader.
+
+ The following examples of formatted output use the following table with
+ a multiline cell, and headers with a multiline cell::
+
+ >>> table = [["eggs",451],["more\nspam",42]]
+ >>> headers = ["item\nname", "qty"]
+
+ ``plain`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="plain"))
+ item qty
+ name
+ eggs 451
+ more 42
+ spam
+
+ ``simple`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="simple"))
+ item qty
+ name
+ ------ -----
+ eggs 451
+ more 42
+ spam
+
+ ``grid`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="grid"))
+ +--------+-------+
+ | item | qty |
+ | name | |
+ +========+=======+
+ | eggs | 451 |
+ +--------+-------+
+ | more | 42 |
+ | spam | |
+ +--------+-------+
+
+ ``fancy_grid`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="fancy_grid"))
+ ╒════════╤═══════╕
+ │ item │ qty │
+ │ name │ │
+ ╞════════╪═══════╡
+ │ eggs │ 451 │
+ ├────────┼───────┤
+ │ more │ 42 │
+ │ spam │ │
+ ╘════════╧═══════╛
+
+ ``pipe`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="pipe"))
+ | item | qty |
+ | name | |
+ |:-------|------:|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+ ``orgtbl`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="orgtbl"))
+ | item | qty |
+ | name | |
+ |--------+-------|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+ ``jira`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="jira"))
+ | item | qty |
+ | name | |
+ |:-------|------:|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+ ``presto`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="presto"))
+ item | qty
+ name |
+ --------+-------
+ eggs | 451
+ more | 42
+ spam |
+
+ ``psql`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="psql"))
+ +--------+-------+
+ | item | qty |
+ | name | |
+ |--------+-------|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+ +--------+-------+
+
+ ``rst`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="rst"))
+ ====== =====
+ item qty
+ name
+ ====== =====
+ eggs 451
+ more 42
+ spam
+ ====== =====
+
+ Multiline cells are not well supported for the other table formats.
+
+
Usage of the command line utility
---------------------------------
@@ -460,7 +622,8 @@ Description: =============== -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
+ rst, mediawiki, html, latex, latex_raw,
+ latex_booktabs, tsv
(default: simple)
@@ -485,26 +648,32 @@ Description: =============== 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``
+ The following mini-benchmark was run in Python 3.5.2 on Windows
::
================================= ========== ===========
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
+ csv to StringIO 14.5 1.0
+ join with tabs and newlines 20.3 1.4
+ asciitable (0.8.0) 355.1 24.5
+ tabulate (0.8.2) 830.3 57.3
+ tabulate (0.8.2, WIDE_CHARS_MODE) 1483.4 102.4
+ PrettyTable (0.7.2) 1611.9 111.2
+ texttable (0.8.8) 1916.5 132.3
================================= ========== ===========
Version history
---------------
- - 0.8: FUTURE RELEASE
+ - 0.8.2: Bug fixes.
+ - 0.8.1: Multiline data in several output formats.
+ New ``latex_raw`` format.
+ Column-specific floating point formatting.
+ Python 3.5 & 3.6 support. Drop support for Python 2.6, 3.2, 3.3 (should still work).
+ - 0.7.7: Identical to 0.7.6, resolving some PyPI issues.
- 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.
@@ -514,17 +683,17 @@ Description: =============== - 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
+ - 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.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.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``,
+ - 0.4: Unicode support, Python3 support, ``rst`` tables.
+ - 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``,
``grid``, ``pipe``, and ``orgtbl``.
@@ -565,7 +734,7 @@ Description: =============== test individual Python versions.
.. _nose: https://nose.readthedocs.org/
- .. _tox: http://tox.testrun.org/
+ .. _tox: https://tox.readthedocs.io/
Contributors
@@ -576,15 +745,19 @@ Description: =============== 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.
+ Zack Dever, Christian Clauss, Benjamin Maier, Andy MacKinlay, Thomas Roten,
+ Jue Wang, Joe King, Samuel Phan, Nick Satterly, Daniel Robbins, Dmitry B,
+ Lars Butler, Andreas Maier, Dick Marinus.
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
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: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Software Development :: Libraries
@@ -49,9 +49,9 @@ On Windows:: 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
+.. image:: https://circleci.com/bb/astanin/python-tabulate.svg?style=svg
+ :alt: Build status
+ :target: https://circleci.com/bb/astanin/python-tabulate/tree/master
Library usage
@@ -155,12 +155,15 @@ Supported table formats are: - "pipe"
- "orgtbl"
- "jira"
+- "presto"
- "psql"
- "rst"
- "mediawiki"
- "moinmoin"
+- "youtrack"
- "html"
- "latex"
+- "latex_raw"
- "latex_booktabs"
- "textile"
@@ -213,6 +216,15 @@ extensions:: │ bacon │ 0 │
╘════════╧═══════╛
+``presto`` is like tables formatted by Presto cli::
+
+ >>> print tabulate.tabulate()
+ item | qty
+ --------+-------
+ spam | 42
+ eggs | 451
+ bacon | 0
+
``psql`` is like tables formatted by Postgres' psql cli::
>>> print tabulate.tabulate()
@@ -289,6 +301,15 @@ wikis:: || eggs || 451 ||
|| bacon || ||
+``youtrack`` format produces a table markup used in Youtrack
+tickets::
+
+ >>> print tabulate(d,headers,tablefmt="youtrack")
+ || item || quantity ||
+ | spam | 41.999 |
+ | eggs | 451 |
+ | bacon | |
+
``textile`` format produces a table markup used in `Textile`_ format::
>>> print tabulate(table, headers, tablefmt='textile')
@@ -309,7 +330,9 @@ wikis:: </tbody>
</table>
-``latex`` format creates a ``tabular`` environment for LaTeX markup::
+``latex`` format creates a ``tabular`` environment for LaTeX markup,
+replacing special characters like ``_`` or ``\`` to their LaTeX
+correspondents::
>>> print tabulate(table, headers, tablefmt="latex")
\begin{tabular}{lr}
@@ -322,6 +345,9 @@ wikis:: \hline
\end{tabular}
+``latex_raw`` behaves like ``latex`` but does not escape LaTeX commands
+and special characters.
+
``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup
using spacing and style from the ``booktabs`` package.
@@ -333,6 +359,7 @@ using spacing and style from the ``booktabs`` package. .. _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
+.. _MoinMoin: https://moinmo.in/
Column alignment
@@ -396,13 +423,33 @@ 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
-- ------
+``floatfmt`` argument can be a list or a tuple of format strings,
+one per column, in which case every column may have different number formatting::
+
+ >>> print tabulate([[0.12345, 0.12345, 0.12345]], floatfmt=(".1f", ".3f"))
+ --- ----- -------
+ 0.1 0.123 0.12345
+ --- ----- -------
+
+
+
+Text formatting
+~~~~~~~~~~~~~~~
+
+By default, ``tabulate`` removes leading and trailing whitespace from text
+columns. To disable whitespace removal, set the global module-level flag
+``PRESERVE_WHITESPACE``::
+
+ import tabulate
+ tabulate.PRESERVE_WHITESPACE = True
+
+
Wide (fullwidth CJK) symbols
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -421,6 +468,141 @@ already installed. To disable wide characters support without uninstalling tabulate.WIDE_CHARS_MODE = False
+Multiline cells
+~~~~~~~~~~~~~~~
+
+Most table formats support multiline cell text (text containing newline
+characters). The newline characters are honored as line break characters.
+
+Multiline cells are supported for data rows and for header rows.
+
+Further automatic line breaks are not inserted. Of course, some output formats
+such as latex or html handle automatic formatting of the cell content on their
+own, but for those that don't, the newline characters in the input cell text
+are the only means to break a line in cell text.
+
+Note that some output formats (e.g. simple, or plain) do not represent row
+delimiters, so that the representation of multiline cells in such formats
+may be ambiguous to the reader.
+
+The following examples of formatted output use the following table with
+a multiline cell, and headers with a multiline cell::
+
+ >>> table = [["eggs",451],["more\nspam",42]]
+ >>> headers = ["item\nname", "qty"]
+
+``plain`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="plain"))
+ item qty
+ name
+ eggs 451
+ more 42
+ spam
+
+``simple`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="simple"))
+ item qty
+ name
+ ------ -----
+ eggs 451
+ more 42
+ spam
+
+``grid`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="grid"))
+ +--------+-------+
+ | item | qty |
+ | name | |
+ +========+=======+
+ | eggs | 451 |
+ +--------+-------+
+ | more | 42 |
+ | spam | |
+ +--------+-------+
+
+``fancy_grid`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="fancy_grid"))
+ ╒════════╤═══════╕
+ │ item │ qty │
+ │ name │ │
+ ╞════════╪═══════╡
+ │ eggs │ 451 │
+ ├────────┼───────┤
+ │ more │ 42 │
+ │ spam │ │
+ ╘════════╧═══════╛
+
+``pipe`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="pipe"))
+ | item | qty |
+ | name | |
+ |:-------|------:|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+``orgtbl`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="orgtbl"))
+ | item | qty |
+ | name | |
+ |--------+-------|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+``jira`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="jira"))
+ | item | qty |
+ | name | |
+ |:-------|------:|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+``presto`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="presto"))
+ item | qty
+ name |
+ --------+-------
+ eggs | 451
+ more | 42
+ spam |
+
+``psql`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="psql"))
+ +--------+-------+
+ | item | qty |
+ | name | |
+ |--------+-------|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+ +--------+-------+
+
+``rst`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="rst"))
+ ====== =====
+ item qty
+ name
+ ====== =====
+ eggs 451
+ more 42
+ spam
+ ====== =====
+
+Multiline cells are not well supported for the other table formats.
+
+
Usage of the command line utility
---------------------------------
@@ -440,7 +622,8 @@ Usage of the command line utility -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
+ rst, mediawiki, html, latex, latex_raw,
+ latex_booktabs, tsv
(default: simple)
@@ -465,26 +648,32 @@ 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``
+The following mini-benchmark was run in Python 3.5.2 on Windows
::
================================= ========== ===========
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
+ csv to StringIO 14.5 1.0
+ join with tabs and newlines 20.3 1.4
+ asciitable (0.8.0) 355.1 24.5
+ tabulate (0.8.2) 830.3 57.3
+ tabulate (0.8.2, WIDE_CHARS_MODE) 1483.4 102.4
+ PrettyTable (0.7.2) 1611.9 111.2
+ texttable (0.8.8) 1916.5 132.3
================================= ========== ===========
Version history
---------------
-- 0.8: FUTURE RELEASE
+- 0.8.2: Bug fixes.
+- 0.8.1: Multiline data in several output formats.
+ New ``latex_raw`` format.
+ Column-specific floating point formatting.
+ Python 3.5 & 3.6 support. Drop support for Python 2.6, 3.2, 3.3 (should still work).
+- 0.7.7: Identical to 0.7.6, resolving some PyPI issues.
- 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.
@@ -494,17 +683,17 @@ Version history - 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
+- 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.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.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``,
+- 0.4: Unicode support, Python3 support, ``rst`` tables.
+- 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``,
``grid``, ``pipe``, and ``orgtbl``.
@@ -545,7 +734,7 @@ 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/
+.. _tox: https://tox.readthedocs.io/
Contributors
@@ -556,4 +745,6 @@ 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.
+Zack Dever, Christian Clauss, Benjamin Maier, Andy MacKinlay, Thomas Roten,
+Jue Wang, Joe King, Samuel Phan, Nick Satterly, Daniel Robbins, Dmitry B,
+Lars Butler, Andreas Maier, Dick Marinus.
diff --git a/benchmark.py b/benchmark.py new file mode 100644 index 0000000..b50e4ff --- /dev/null +++ b/benchmark.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from __future__ import print_function
+from timeit import timeit
+import tabulate
+import asciitable
+import prettytable
+import texttable
+import sys
+import codecs
+
+setup_code = r"""
+from csv import writer
+try: # Python 2
+ from StringIO import StringIO
+except: # Python 3
+ from io import StringIO
+import tabulate
+import asciitable
+import prettytable
+import texttable
+
+
+import platform
+if platform.platform().startswith("Windows") \
+ and \
+ platform.python_version_tuple() < ('3','6','0'):
+ import win_unicode_console
+ win_unicode_console.enable()
+
+
+table=[["some text"]+list(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, widechars=False):
+ tabulate.WIDE_CHARS_MODE = tabulate.wcwidth is not None and widechars
+ 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"tabulate (%s, WIDE_CHARS_MODE)" % tabulate.__version__, "run_tabulate(table, widechars=True)"),
+ (u"PrettyTable (%s)" % prettytable.__version__, "run_prettytable(table)"),
+ (u"texttable (%s)" % texttable.__version__, "run_texttable(table)"),
+ ]
+
+
+if tabulate.wcwidth is None:
+ del(methods[4])
+
+
+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")
+
+ import platform
+ if platform.platform().startswith("Windows"):
+ print(table)
+ else:
+ 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/changelog b/debian/changelog index 8510e83..08868cc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +python-tabulate (0.8.2-1) unstable; urgency=medium + + * New maintainer. Closes: #888214 + * New upstream release + + -- Yago González <yagogonzalezg@gmail.com> Sat, 17 Mar 2018 20:57:26 +0100 + python-tabulate (0.7.7-1) unstable; urgency=medium [ Sandro Tosi ] diff --git a/debian/control b/debian/control index 0c7ed52..365c582 100644 --- a/debian/control +++ b/debian/control @@ -1,6 +1,6 @@ Source: python-tabulate Priority: optional -Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org> +Maintainer: Yago González <yagogonzalezg@gmail.com> Uploaders: ChangZhuo Chen (陳昌倬) <czchen@debian.org>, Sandro Tosi <morph@debian.org> Build-Depends: debhelper (>= 10), @@ -1,5 +1,4 @@ [egg_info]
tag_build =
tag_date = 0
-tag_svn_revision = 0
@@ -10,10 +10,6 @@ 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("`_", "`")
@@ -41,21 +37,23 @@ else: setup(name='tabulate',
- version='0.7.7',
+ version='0.8.2',
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,
+ license='MIT',
classifiers= [ "Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
- "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
- "Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
"Topic :: Software Development :: Libraries" ],
py_modules = ['tabulate'],
entry_points = {'console_scripts': console_scripts},
diff --git a/tabulate.egg-info/.PKG-INFO.swp b/tabulate.egg-info/.PKG-INFO.swp Binary files differdeleted file mode 100644 index f01081e..0000000 --- a/tabulate.egg-info/.PKG-INFO.swp +++ /dev/null diff --git a/tabulate.egg-info/PKG-INFO b/tabulate.egg-info/PKG-INFO index dda9777..bf60834 100644 --- a/tabulate.egg-info/PKG-INFO +++ b/tabulate.egg-info/PKG-INFO @@ -1,31 +1,11 @@ Metadata-Version: 1.1
Name: tabulate
-Version: 0.7.7
+Version: 0.8.2
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.
-
+License: MIT
Description: ===============
python-tabulate
===============
@@ -175,12 +155,15 @@ Description: =============== - "pipe"
- "orgtbl"
- "jira"
+ - "presto"
- "psql"
- "rst"
- "mediawiki"
- "moinmoin"
+ - "youtrack"
- "html"
- "latex"
+ - "latex_raw"
- "latex_booktabs"
- "textile"
@@ -233,6 +216,15 @@ Description: =============== │ bacon │ 0 │
╘════════╧═══════╛
+ ``presto`` is like tables formatted by Presto cli::
+
+ >>> print tabulate.tabulate()
+ item | qty
+ --------+-------
+ spam | 42
+ eggs | 451
+ bacon | 0
+
``psql`` is like tables formatted by Postgres' psql cli::
>>> print tabulate.tabulate()
@@ -309,6 +301,15 @@ Description: =============== || eggs || 451 ||
|| bacon || ||
+ ``youtrack`` format produces a table markup used in Youtrack
+ tickets::
+
+ >>> print tabulate(d,headers,tablefmt="youtrack")
+ || item || quantity ||
+ | spam | 41.999 |
+ | eggs | 451 |
+ | bacon | |
+
``textile`` format produces a table markup used in `Textile` format::
>>> print tabulate(table, headers, tablefmt='textile')
@@ -329,7 +330,9 @@ Description: =============== </tbody>
</table>
- ``latex`` format creates a ``tabular`` environment for LaTeX markup::
+ ``latex`` format creates a ``tabular`` environment for LaTeX markup,
+ replacing special characters like ```` or ``\`` to their LaTeX
+ correspondents::
>>> print tabulate(table, headers, tablefmt="latex")
\begin{tabular}{lr}
@@ -342,6 +345,9 @@ Description: =============== \hline
\end{tabular}
+ ``latex_raw`` behaves like ``latex`` but does not escape LaTeX commands
+ and special characters.
+
``latex_booktabs`` creates a ``tabular`` environment for LaTeX markup
using spacing and style from the ``booktabs`` package.
@@ -353,6 +359,7 @@ Description: =============== .. _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
+ .. _MoinMoin: https://moinmo.in/
Column alignment
@@ -416,13 +423,33 @@ Description: =============== ``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
-- ------
+ ``floatfmt`` argument can be a list or a tuple of format strings,
+ one per column, in which case every column may have different number formatting::
+
+ >>> print tabulate([[0.12345, 0.12345, 0.12345]], floatfmt=(".1f", ".3f"))
+ --- ----- -------
+ 0.1 0.123 0.12345
+ --- ----- -------
+
+
+
+ Text formatting
+ ~~~~~~~~~~~~~~~
+
+ By default, ``tabulate`` removes leading and trailing whitespace from text
+ columns. To disable whitespace removal, set the global module-level flag
+ ``PRESERVE_WHITESPACE``::
+
+ import tabulate
+ tabulate.PRESERVE_WHITESPACE = True
+
+
Wide (fullwidth CJK) symbols
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -441,6 +468,141 @@ Description: =============== tabulate.WIDE_CHARS_MODE = False
+ Multiline cells
+ ~~~~~~~~~~~~~~~
+
+ Most table formats support multiline cell text (text containing newline
+ characters). The newline characters are honored as line break characters.
+
+ Multiline cells are supported for data rows and for header rows.
+
+ Further automatic line breaks are not inserted. Of course, some output formats
+ such as latex or html handle automatic formatting of the cell content on their
+ own, but for those that don't, the newline characters in the input cell text
+ are the only means to break a line in cell text.
+
+ Note that some output formats (e.g. simple, or plain) do not represent row
+ delimiters, so that the representation of multiline cells in such formats
+ may be ambiguous to the reader.
+
+ The following examples of formatted output use the following table with
+ a multiline cell, and headers with a multiline cell::
+
+ >>> table = [["eggs",451],["more\nspam",42]]
+ >>> headers = ["item\nname", "qty"]
+
+ ``plain`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="plain"))
+ item qty
+ name
+ eggs 451
+ more 42
+ spam
+
+ ``simple`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="simple"))
+ item qty
+ name
+ ------ -----
+ eggs 451
+ more 42
+ spam
+
+ ``grid`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="grid"))
+ +--------+-------+
+ | item | qty |
+ | name | |
+ +========+=======+
+ | eggs | 451 |
+ +--------+-------+
+ | more | 42 |
+ | spam | |
+ +--------+-------+
+
+ ``fancy_grid`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="fancy_grid"))
+ ╒════════╤═══════╕
+ │ item │ qty │
+ │ name │ │
+ ╞════════╪═══════╡
+ │ eggs │ 451 │
+ ├────────┼───────┤
+ │ more │ 42 │
+ │ spam │ │
+ ╘════════╧═══════╛
+
+ ``pipe`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="pipe"))
+ | item | qty |
+ | name | |
+ |:-------|------:|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+ ``orgtbl`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="orgtbl"))
+ | item | qty |
+ | name | |
+ |--------+-------|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+ ``jira`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="jira"))
+ | item | qty |
+ | name | |
+ |:-------|------:|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+
+ ``presto`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="presto"))
+ item | qty
+ name |
+ --------+-------
+ eggs | 451
+ more | 42
+ spam |
+
+ ``psql`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="psql"))
+ +--------+-------+
+ | item | qty |
+ | name | |
+ |--------+-------|
+ | eggs | 451 |
+ | more | 42 |
+ | spam | |
+ +--------+-------+
+
+ ``rst`` tables::
+
+ >>> print(tabulate(table, headers, tablefmt="rst"))
+ ====== =====
+ item qty
+ name
+ ====== =====
+ eggs 451
+ more 42
+ spam
+ ====== =====
+
+ Multiline cells are not well supported for the other table formats.
+
+
Usage of the command line utility
---------------------------------
@@ -460,7 +622,8 @@ Description: =============== -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
+ rst, mediawiki, html, latex, latex_raw,
+ latex_booktabs, tsv
(default: simple)
@@ -485,26 +648,32 @@ Description: =============== 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``
+ The following mini-benchmark was run in Python 3.5.2 on Windows
::
================================= ========== ===========
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
+ csv to StringIO 14.5 1.0
+ join with tabs and newlines 20.3 1.4
+ asciitable (0.8.0) 355.1 24.5
+ tabulate (0.8.2) 830.3 57.3
+ tabulate (0.8.2, WIDE_CHARS_MODE) 1483.4 102.4
+ PrettyTable (0.7.2) 1611.9 111.2
+ texttable (0.8.8) 1916.5 132.3
================================= ========== ===========
Version history
---------------
- - 0.8: FUTURE RELEASE
+ - 0.8.2: Bug fixes.
+ - 0.8.1: Multiline data in several output formats.
+ New ``latex_raw`` format.
+ Column-specific floating point formatting.
+ Python 3.5 & 3.6 support. Drop support for Python 2.6, 3.2, 3.3 (should still work).
+ - 0.7.7: Identical to 0.7.6, resolving some PyPI issues.
- 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.
@@ -514,17 +683,17 @@ Description: =============== - 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
+ - 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.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.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``,
+ - 0.4: Unicode support, Python3 support, ``rst`` tables.
+ - 0.3: Initial PyPI release. Table formats: ``simple``, ``plain``,
``grid``, ``pipe``, and ``orgtbl``.
@@ -565,7 +734,7 @@ Description: =============== test individual Python versions.
.. _nose: https://nose.readthedocs.org/
- .. _tox: http://tox.testrun.org/
+ .. _tox: https://tox.readthedocs.io/
Contributors
@@ -576,15 +745,19 @@ Description: =============== 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.
+ Zack Dever, Christian Clauss, Benjamin Maier, Andy MacKinlay, Thomas Roten,
+ Jue Wang, Joe King, Samuel Phan, Nick Satterly, Daniel Robbins, Dmitry B,
+ Lars Butler, Andreas Maier, Dick Marinus.
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
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: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
Classifier: Topic :: Software Development :: Libraries
diff --git a/tabulate.egg-info/SOURCES.txt b/tabulate.egg-info/SOURCES.txt index c8cb200..b7a2d92 100644 --- a/tabulate.egg-info/SOURCES.txt +++ b/tabulate.egg-info/SOURCES.txt @@ -2,17 +2,19 @@ LICENSE MANIFEST.in README README.rst +benchmark.py 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/common.py test/test_api.py test/test_cli.py test/test_input.py +test/test_internal.py test/test_output.py test/test_regression.py
\ No newline at end of file diff --git a/tabulate.py b/tabulate.py index de81033..2d6d150 100644 --- a/tabulate.py +++ b/tabulate.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals from collections import namedtuple, Iterable
from platform import python_version_tuple
import re
+import math
if python_version_tuple()[0] < "3":
@@ -33,6 +34,7 @@ else: _float_type = float
_text_type = str
_binary_type = bytes
+ basestring = str
import io
def _is_file(f):
@@ -45,12 +47,19 @@ except ImportError: __all__ = ["tabulate", "tabulate_formats", "simple_separated_format"]
-__version__ = "0.7.7"
+__version__ = "0.8.2"
# minimum extra space in headers
MIN_PADDING = 2
+# Whether or not to preserve leading/trailing whitespace in data.
+PRESERVE_WHITESPACE = False
+
+_DEFAULT_FLOATFMT="g"
+_DEFAULT_MISSINGVAL=""
+
+
# if True, enable wide-character (CJK) support
WIDE_CHARS_MODE = wcwidth is not None
@@ -182,18 +191,16 @@ LATEX_ESCAPE_RULES = {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 _latex_row(cell_values, colwidths, colaligns, escrules=LATEX_ESCAPE_RULES):
def escape_char(c):
- return LATEX_ESCAPE_RULES.get(c, c)
+ return escrules.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 "":
+ if isinstance(val, (_text_type, _binary_type)) and not val.strip():
return ".."
else:
return val
@@ -265,6 +272,14 @@ _table_formats = {"simple": headerrow=DataRow("||", "||", "||"),
datarow=DataRow("|", "|", "|"),
padding=1, with_header_hide=None),
+ "presto":
+ 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("|", "-", "+", "|"),
@@ -298,6 +313,14 @@ _table_formats = {"simple": headerrow=partial(_moin_row_with_attrs,"||",header="'''"),
datarow=partial(_moin_row_with_attrs,"||"),
padding=1, with_header_hide=None),
+ "youtrack":
+ TableFormat(lineabove=None,
+ linebelowheader=None,
+ linebetweenrows=None,
+ linebelow=None,
+ headerrow=DataRow("|| ", " || ", " || "),
+ datarow=DataRow("| ", " | ", " |"),
+ padding=1, with_header_hide=None),
"html":
TableFormat(lineabove=_html_begin_table_without_header,
linebelowheader="",
@@ -314,6 +337,14 @@ _table_formats = {"simple": headerrow=_latex_row,
datarow=_latex_row,
padding=1, with_header_hide=None),
+ "latex_raw":
+ TableFormat(lineabove=_latex_line_begin_tabular,
+ linebelowheader=Line("\\hline", "", "", ""),
+ linebetweenrows=None,
+ linebelow=Line("\\hline\n\\end{tabular}", "", "", ""),
+ headerrow=partial(_latex_row, escrules={}),
+ datarow=partial(_latex_row, escrules={}),
+ padding=1, with_header_hide=None),
"latex_booktabs":
TableFormat(lineabove=partial(_latex_line_begin_tabular, booktabs=True),
linebelowheader=Line("\\midrule", "", "", ""),
@@ -338,7 +369,34 @@ _table_formats = {"simple": tabulate_formats = list(sorted(_table_formats.keys()))
-
+# The table formats for which multiline cells will be folded into subsequent
+# table rows. The key is the original format specified at the API. The value is
+# the format that will be used to represent the original format.
+multiline_formats = {
+ "plain": "plain",
+ "simple": "simple",
+ "grid": "grid",
+ "fancy_grid": "fancy_grid",
+ "pipe": "pipe",
+ "orgtbl": "orgtbl",
+ "jira": "jira",
+ "presto": "presto",
+ "psql": "psql",
+ "rst": "rst",
+}
+
+# TODO: Add multiline support for the remaining table formats:
+# - mediawiki: Replace \n with <br>
+# - moinmoin: TBD
+# - youtrack: TBD
+# - html: Replace \n with <br>
+# - latex*: Use "makecell" package: In header, replace X\nY with
+# \thead{X\\Y} and in data row, replace X\nY with \makecell{X\\Y}
+# - tsv: TBD
+# - textile: Replace \n with <br/> (must be well-formed XML)
+
+_multiline_codes = re.compile(r"\r|\n|\r\n")
+_multiline_codes_bytes = re.compile(b"\r|\n|\r\n")
_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
@@ -373,8 +431,17 @@ def _isnumber(string): True
>>> _isnumber("spam")
False
+ >>> _isnumber("123e45678")
+ False
+ >>> _isnumber("inf")
+ True
"""
- return _isconvertible(float, string)
+ if not _isconvertible(float, string):
+ return False
+ elif isinstance(string, (_text_type, _binary_type)) and (
+ math.isinf(float(string)) or math.isnan(float(string))):
+ return string.lower() in ['inf', '-inf', 'nan']
+ return True
def _isint(string, inttype=int):
@@ -503,6 +570,10 @@ def _padboth(width, s): return fmt.format(s)
+def _padnone(ignore_width, s):
+ return s
+
+
def _strip_invisible(s):
"Remove invisible ANSI color codes."
if isinstance(s, _text_type):
@@ -529,21 +600,41 @@ def _visible_width(s): return len_fn(_text_type(s))
-def _align_column(strings, alignment, minwidth=0, has_invisible=True):
- """[string] -> [padded_string]
+def _is_multiline(s):
+ if isinstance(s, _text_type):
+ return bool(re.search(_multiline_codes, s))
+ else: # a bytestring
+ return bool(re.search(_multiline_codes_bytes, s))
- >>> 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']
+def _multiline_width(multiline_s, line_width_fn=len):
+ """Visible width of a potentially multiline content."""
+ return max(map(line_width_fn, re.split("[\r\n]", multiline_s)))
- """
+
+def _choose_width_fn(has_invisible, enable_widechars, is_multiline):
+ """Return a function to calculate visible cell width."""
+ if has_invisible:
+ line_width_fn = _visible_width
+ elif enable_widechars: # optional wide-character support if available
+ line_width_fn = wcwidth.wcswidth
+ else:
+ line_width_fn = len
+ if is_multiline:
+ width_fn = lambda s: _multiline_width(s, line_width_fn)
+ else:
+ width_fn = line_width_fn
+ return width_fn
+
+
+def _align_column_choose_padfn(strings, alignment, has_invisible):
if alignment == "right":
- strings = [s.strip() for s in strings]
+ if not PRESERVE_WHITESPACE:
+ strings = [s.strip() for s in strings]
padfn = _padleft
elif alignment == "center":
- strings = [s.strip() for s in strings]
+ if not PRESERVE_WHITESPACE:
+ strings = [s.strip() for s in strings]
padfn = _padboth
elif alignment == "decimal":
if has_invisible:
@@ -555,30 +646,46 @@ def _align_column(strings, alignment, minwidth=0, has_invisible=True): for s, decs in zip(strings, decimals)]
padfn = _padleft
elif not alignment:
- return strings
+ padfn = _padnone
else:
- strings = [s.strip() for s in strings]
+ if not PRESERVE_WHITESPACE:
+ strings = [s.strip() for s in strings]
padfn = _padright
+ return strings, padfn
- 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))
+def _align_column(strings, alignment, minwidth=0,
+ has_invisible=True, enable_widechars=False, is_multiline=False):
+ """[string] -> [padded_string]"""
+ strings, padfn = _align_column_choose_padfn(strings, alignment, has_invisible)
+ width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline)
+
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)]
+ # TODO: refactor column alignment in single-line and multiline modes
+ if is_multiline:
+ if not enable_widechars and not has_invisible:
+ padded_strings = [
+ "\n".join([padfn(maxwidth, s) for s in ms.splitlines()])
+ for ms in strings]
+ else:
+ # enable wide-character width corrections
+ s_lens = [max((len(s) for s in re.split("[\r\n]", ms))) for ms in strings]
+ 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 = ["\n".join([padfn(w, s) for s in (ms.splitlines() or ms)])
+ for ms, w in zip(strings, visible_widths)]
+ else: # single-line cell values
+ if not enable_widechars and not has_invisible:
+ padded_strings = [padfn(maxwidth, s) for s in strings]
+ else:
+ # enable wide-character width corrections
+ s_lens = list(map(len, strings))
+ 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
@@ -649,9 +756,15 @@ def _format(val, valtype, floatfmt, missingval="", has_invisible=True): return "{0}".format(val)
-def _align_header(header, alignment, width, visible_width):
+def _align_header(header, alignment, width, visible_width, is_multiline=False, width_fn=None):
"Pad string header to width chars given known visible_width of the header."
- width += len(header) - visible_width
+ if is_multiline:
+ header_lines = re.split(_multiline_codes, header)
+ padded_lines = [_align_header(h, alignment, width, width_fn(h)) for h in header_lines]
+ return "\n".join(padded_lines)
+ # else: not multiline
+ ninvisible = len(header) - visible_width
+ width += ninvisible
if alignment == "left":
return _padright(width, header)
elif alignment == "center":
@@ -842,9 +955,10 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"): return rows, headers
+
def tabulate(tabular_data, headers=(), tablefmt="simple",
- floatfmt="g", numalign="decimal", stralign="left",
- missingval="", showindex="default", disable_numparse=False):
+ floatfmt=_DEFAULT_FLOATFMT, numalign="decimal", stralign="left",
+ missingval=_DEFAULT_MISSINGVAL, showindex="default", disable_numparse=False):
"""Format a fixed width table for pretty printing.
>>> print(tabulate([[1, 2.34], [-56, "8.999"], ["2", "10001"]]))
@@ -911,9 +1025,12 @@ def tabulate(tabular_data, headers=(), tablefmt="simple", -------------
`floatfmt` is a format specification used for columns which
- contain numeric data with a decimal point.
+ contain numeric data with a decimal point. This can also be
+ a list or tuple of format strings, one per column.
- `None` values are replaced with a `missingval` string:
+ `None` values are replaced with a `missingval` string (like
+ `floatfmt`, this can also be a list of values for different
+ columns):
>>> print(tabulate([["spam", 1, None],
... ["eggs", 42, 3.14],
@@ -926,8 +1043,8 @@ def tabulate(tabular_data, headers=(), tablefmt="simple", 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.
+ 'latex', 'latex_raw' 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:
@@ -999,6 +1116,15 @@ def tabulate(tabular_data, headers=(), tablefmt="simple", | spam | 41.9999 |
| eggs | 451 |
+ "presto" is like tables produce by the Presto CLI:
+
+ >>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]],
+ ... ["strings", "numbers"], "presto"))
+ strings | numbers
+ -----------+-----------
+ spam | 41.9999
+ eggs | 451
+
>>> print(tabulate([["spam", 41.9999], ["eggs", "451.0"]], tablefmt="pipe"))
|:-----|---------:|
| spam | 41.9999 |
@@ -1078,6 +1204,18 @@ def tabulate(tabular_data, headers=(), tablefmt="simple", \\hline
\\end{tabular}
+ "latex_raw" is similar to "latex", but doesn't escape special characters,
+ such as backslash and underscore, so LaTeX commands may embedded into
+ cells' values:
+
+ >>> print(tabulate([["spam$_9$", 41.9999], ["\\\\emph{eggs}", "451.0"]], tablefmt="latex_raw"))
+ \\begin{tabular}{lr}
+ \\hline
+ spam$_9$ & 41.9999 \\\\
+ \\emph{eggs} & 451 \\\\
+ \\hline
+ \\end{tabular}
+
"latex_booktabs" produces a tabular environment of LaTeX document markup
using the booktabs.sty package:
@@ -1115,48 +1253,60 @@ def tabulate(tabular_data, headers=(), tablefmt="simple", # 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))] + \
+ plain_text = '\t'.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
+ if tablefmt in multiline_formats and _is_multiline(plain_text):
+ tablefmt = multiline_formats.get(tablefmt, tablefmt)
+ is_multiline = True
else:
- width_fn = len
+ is_multiline = False
+ width_fn = _choose_width_fn(has_invisible, enable_widechars, is_multiline)
# 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)]
+ if isinstance(floatfmt, basestring): #old version
+ float_formats = len(cols) * [floatfmt] # just duplicate the string to use in each column
+ else: # if floatfmt is list, tuple etc we have one per column
+ float_formats = list(floatfmt)
+ if len(float_formats) < len(cols):
+ float_formats.extend( (len(cols)-len(float_formats)) * [_DEFAULT_FLOATFMT] )
+ if isinstance(missingval, basestring):
+ missing_vals = len(cols) * [missingval]
+ else:
+ missing_vals = list(missingval)
+ if len(missing_vals) < len(cols):
+ missing_vals.extend( (len(cols)-len(missing_vals)) * [_DEFAULT_MISSINGVAL] )
+ cols = [[_format(v, ct, fl_fmt, miss_v, has_invisible) for v in c]
+ for c, ct, fl_fmt, miss_v in zip(cols, coltypes, float_formats, missing_vals)]
# 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)
+ cols = [_align_column(c, a, minw, has_invisible, enable_widechars, is_multiline)
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))
+ minwidths = [max(minw, max(width_fn(cl) for cl in c)) for minw, c in zip(minwidths, t_cols)]
+ headers = [_align_header(h, a, minw, width_fn(h), is_multiline, width_fn)
for h, a, minw in zip(headers, t_aligns, minwidths)]
rows = list(zip(*cols))
else:
- minwidths = [width_fn(c[0]) for c in cols]
+ minwidths = [max(width_fn(cl) for cl in c) 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)
+ return _format_table(tablefmt, headers, rows, minwidths, aligns, is_multiline)
def _expand_numparse(disable_numparse, column_count):
@@ -1176,6 +1326,15 @@ def _expand_numparse(disable_numparse, column_count): return [not disable_numparse] * column_count
+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 _build_simple_row(padded_cells, rowfmt):
"Format row according to DataRow format without padding."
begin, sep, end = rowfmt
@@ -1192,6 +1351,24 @@ def _build_row(padded_cells, colwidths, colaligns, rowfmt): return _build_simple_row(padded_cells, rowfmt)
+def _append_basic_row(lines, padded_cells, colwidths, colaligns, rowfmt):
+ lines.append(_build_row(padded_cells, colwidths, colaligns, rowfmt))
+ return lines
+
+
+def _append_multiline_row(lines, padded_multiline_cells, padded_widths, colaligns, rowfmt, pad):
+ colwidths = [w - 2*pad for w in padded_widths]
+ cells_lines = [c.splitlines() for c in padded_multiline_cells]
+ nlines = max(map(len, cells_lines)) # number of lines in the row
+ # vertically pad cells where some lines are missing
+ cells_lines = [(cl + [' '*w]*(nlines - len(cl))) for cl, w in zip(cells_lines, colwidths)]
+ lines_cells = [[cl[i] for cl in cells_lines] for i in range(nlines)]
+ for ln in lines_cells:
+ padded_ln = _pad_row(ln, pad)
+ _append_basic_row(lines, padded_ln, colwidths, colaligns, rowfmt)
+ return lines
+
+
def _build_line(colwidths, colaligns, linefmt):
"Return a string which represents a horizontal line."
if not linefmt:
@@ -1204,16 +1381,12 @@ def _build_line(colwidths, colaligns, linefmt): 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 _append_line(lines, colwidths, colaligns, linefmt):
+ lines.append(_build_line(colwidths, colaligns, linefmt))
+ return lines
-def _format_table(fmt, headers, rows, colwidths, colaligns):
+def _format_table(fmt, headers, rows, colwidths, colaligns, is_multiline):
"""Produce a plain-text representation of the table."""
lines = []
hidden = fmt.with_header_hide if (headers and fmt.with_header_hide) else []
@@ -1221,30 +1394,37 @@ def _format_table(fmt, headers, rows, colwidths, colaligns): 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 is_multiline:
+ pad_row = lambda row, _: row # do it later, in _append_multiline_row
+ append_row = partial(_append_multiline_row, pad=pad)
+ else:
+ pad_row = _pad_row
+ append_row = _append_basic_row
+
+ 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))
+ _append_line(lines, padded_widths, colaligns, fmt.lineabove)
if padded_headers:
- lines.append(_build_row(padded_headers, padded_widths, colaligns, headerrow))
+ append_row(lines, padded_headers, padded_widths, colaligns, headerrow)
if fmt.linebelowheader and "linebelowheader" not in hidden:
- lines.append(_build_line(padded_widths, colaligns, fmt.linebelowheader))
+ _append_line(lines, 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))
+ append_row(lines, row, padded_widths, colaligns, fmt.datarow)
+ _append_line(lines, padded_widths, colaligns, fmt.linebetweenrows)
# the last row without a line below
- lines.append(_build_row(padded_rows[-1], padded_widths, colaligns, fmt.datarow))
+ append_row(lines, padded_rows[-1], padded_widths, colaligns, fmt.datarow)
else:
for row in padded_rows:
- lines.append(_build_row(row, padded_widths, colaligns, fmt.datarow))
+ append_row(lines, row, padded_widths, colaligns, fmt.datarow)
if fmt.linebelow and "linebelow" not in hidden:
- lines.append(_build_line(padded_widths, colaligns, fmt.linebelow))
+ _append_line(lines, padded_widths, colaligns, fmt.linebelow)
if headers or rows:
return "\n".join(lines)
@@ -1271,7 +1451,8 @@ def _main(): -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
+ rst, mediawiki, html, latex, latex_raw,
+ latex_booktabs, tsv
(default: simple)
"""
import getopt
@@ -1287,7 +1468,7 @@ def _main(): print(usage)
sys.exit(2)
headers = []
- floatfmt = "g"
+ floatfmt = _DEFAULT_FLOATFMT
tablefmt = "simple"
sep = r"\s+"
outfile = "-"
diff --git a/test/common.py b/test/common.py new file mode 100644 index 0000000..1ef4fbe --- /dev/null +++ b/test/common.py @@ -0,0 +1,46 @@ +try:
+ from nose.plugins.skip import SkipTest
+except ImportError:
+ try:
+ from unittest.case import SkipTest # Python >= 2.7
+ except ImportError:
+ try:
+ from unittest2.case import SkipTest # Python < 2.7
+ except ImportError:
+ class SkipTest(Exception):
+ """Raise this exception to mark a test as skipped.
+ """
+ pass
+
+
+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 d8e6dad..2023a81 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -12,6 +12,9 @@ from common import SkipTest try:
if python_version_tuple() >= ('3','3','0'):
from inspect import signature, _empty
+ else:
+ signature = None
+ _empty = None
except ImportError:
signature = None
_empty = None
diff --git a/test/test_internal.py b/test/test_internal.py new file mode 100644 index 0000000..0e5d7e9 --- /dev/null +++ b/test/test_internal.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*-
+
+"""Tests of the internal tabulate functions."""
+
+from __future__ import print_function
+from __future__ import unicode_literals
+import tabulate as T
+from common import assert_equal, assert_in, assert_raises, SkipTest
+
+
+def test_multiline_width():
+ "Internal: _multiline_width()"
+ multiline_string = "\n".join(["foo", "barbaz", "spam"])
+ assert_equal(T._multiline_width(multiline_string), 6)
+ oneline_string = "12345"
+ assert_equal(T._multiline_width(oneline_string), len(oneline_string))
+
+
+def test_align_column_decimal():
+ "Internal: _align_column(..., 'decimal')"
+ column = ["12.345", "-1234.5", "1.23", "1234.5", "1e+234", "1.0e234"]
+ output = T._align_column(column, "decimal")
+ expected = [
+ ' 12.345 ',
+ '-1234.5 ',
+ ' 1.23 ',
+ ' 1234.5 ',
+ ' 1e+234 ',
+ ' 1.0e234']
+ assert_equal(output, expected)
+
+
+def test_align_column_none():
+ "Internal: _align_column(..., None)"
+ column = ['123.4', '56.7890']
+ output = T._align_column(column, None)
+ expected = ['123.4', '56.7890']
+ assert_equal(output, expected)
+
+
+def test_align_column_multiline():
+ "Internal: _align_column(..., is_multiline=True)"
+ column = ["1", "123", "12345\n6"]
+ output = T._align_column(column, "center", is_multiline=True)
+ expected = [
+ " 1 ",
+ " 123 ",
+ "12345" + "\n" +
+ " 6 "]
+ assert_equal(output, expected)
diff --git a/test/test_output.py b/test/test_output.py index cc68cdd..cee333f 100644 --- a/test/test_output.py +++ b/test/test_output.py @@ -4,6 +4,7 @@ from __future__ import print_function
from __future__ import unicode_literals
+import tabulate as tabulate_module
from tabulate import tabulate, simple_separated_format
from common import assert_equal, assert_raises, SkipTest
@@ -33,6 +34,64 @@ def test_plain_headerless(): assert_equal(expected, result)
+def test_plain_multiline_headerless():
+ "Output: plain with multiline cells without headers"
+ table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+ expected = "\n".join([
+ "foo bar hello",
+ " baz",
+ " bau",
+ " multiline",
+ " world"])
+ result = tabulate(table, stralign="center", tablefmt="plain")
+ assert_equal(expected, result)
+
+
+def test_plain_multiline():
+ "Output: plain with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ " more more spam",
+ " spam \x1b[31meggs\x1b[0m & eggs",
+ " 2 foo",
+ " bar"])
+ result = tabulate(table, headers, tablefmt="plain")
+ assert_equal(expected, result)
+
+
+def test_plain_multiline_with_empty_cells():
+ "Output: plain with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ " hdr data fold",
+ " 1",
+ " 2 very long data fold",
+ " this"])
+ result = tabulate(table, headers="firstrow", tablefmt="plain")
+ assert_equal(expected, result)
+
+
+def test_plain_multiline_with_empty_cells_headerless():
+ "Output: plain with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "0",
+ "1",
+ "2 very long data fold",
+ " this"])
+ result = tabulate(table, tablefmt="plain")
+ assert_equal(expected, result)
+
+
def test_simple():
"Output: simple with headers"
expected = "\n".join(['strings numbers',
@@ -43,6 +102,21 @@ def test_simple(): assert_equal(expected, result)
+def test_simple_multiline():
+ "Output: simple with multiline cells"
+ expected = "\n".join([
+ " key value",
+ "----- ---------",
+ " foo bar",
+ "spam multiline",
+ " world",
+ ])
+ table = [["key", "value"], ["foo", "bar"], ["spam", "multiline\nworld"]]
+ result = tabulate(table, headers='firstrow', stralign="center",
+ tablefmt="simple")
+ assert_equal(expected, result)
+
+
def test_simple_headerless():
"Output: simple without headers"
expected = "\n".join(['---- --------',
@@ -53,19 +127,103 @@ def test_simple_headerless(): assert_equal(expected, result)
+def test_simple_multiline_headerless():
+ "Output: simple with multiline cells without headers"
+ table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+ expected = "\n".join([
+ "------- ---------",
+ "foo bar hello",
+ " baz",
+ " bau",
+ " multiline",
+ " world",
+ "------- ---------",])
+ result = tabulate(table, stralign="center", tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_simple_multiline():
+ "Output: simple with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ " more more spam",
+ " spam \x1b[31meggs\x1b[0m & eggs",
+ "----------- -----------",
+ " 2 foo",
+ " bar"])
+ result = tabulate(table, headers, tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_simple_multiline_with_empty_cells():
+ "Output: simple with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ " hdr data fold",
+ "----- -------------- ------",
+ " 1",
+ " 2 very long data fold",
+ " this"])
+ result = tabulate(table, headers="firstrow", tablefmt="simple")
+ assert_equal(expected, result)
+
+
+def test_simple_multiline_with_empty_cells_headerless():
+ "Output: simple with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "- -------------- ----",
+ "0",
+ "1",
+ "2 very long data fold",
+ " this",
+ "- -------------- ----",])
+ result = tabulate(table, tablefmt="simple")
+ assert_equal(expected, result)
+
+
def test_grid():
"Output: grid with headers"
expected = '\n'.join(['+-----------+-----------+',
'| strings | numbers |',
'+===========+===========+',
- '| spam | 41.9999 |',
+ '| spam | 41.9999 |',
'+-----------+-----------+',
- '| eggs | 451 |',
+ '| eggs | 451 |',
'+-----------+-----------+',])
result = tabulate(_test_table, _test_table_headers, tablefmt="grid")
assert_equal(expected, result)
+def test_grid_wide_characters():
+ "Output: grid with wide characters in headers"
+ try:
+ import wcwidth
+ except ImportError:
+ print("test_grid_wide_characters is skipped")
+ raise SkipTest() # this test is optional
+ headers = list(_test_table_headers)
+ headers[1] = '配列'
+ expected = '\n'.join(['+-----------+----------+',
+ '| strings | 配列 |',
+ '+===========+==========+',
+ '| spam | 41.9999 |',
+ '+-----------+----------+',
+ '| eggs | 451 |',
+ '+-----------+----------+',])
+ result = tabulate(_test_table, headers, tablefmt="grid")
+ assert_equal(expected, result)
+
+
def test_grid_headerless():
"Output: grid without headers"
expected = '\n'.join(['+------+----------+',
@@ -77,6 +235,78 @@ def test_grid_headerless(): assert_equal(expected, result)
+def test_grid_multiline_headerless():
+ "Output: grid with multiline cells without headers"
+ table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+ expected = "\n".join([
+ "+---------+-----------+",
+ "| foo bar | hello |",
+ "| baz | |",
+ "| bau | |",
+ "+---------+-----------+",
+ "| | multiline |",
+ "| | world |",
+ "+---------+-----------+"])
+ result = tabulate(table, stralign="center", tablefmt="grid")
+ assert_equal(expected, result)
+
+
+def test_grid_multiline():
+ "Output: grid with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ "+-------------+-------------+",
+ "| more | more spam |",
+ "| spam \x1b[31meggs\x1b[0m | & eggs |",
+ "+=============+=============+",
+ "| 2 | foo |",
+ "| | bar |",
+ "+-------------+-------------+"])
+ result = tabulate(table, headers, tablefmt="grid")
+ assert_equal(expected, result)
+
+
+def test_grid_multiline_with_empty_cells():
+ "Output: grid with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "+-------+----------------+--------+",
+ "| hdr | data | fold |",
+ "+=======+================+========+",
+ "| 1 | | |",
+ "+-------+----------------+--------+",
+ "| 2 | very long data | fold |",
+ "| | | this |",
+ "+-------+----------------+--------+"])
+ result = tabulate(table, headers="firstrow", tablefmt="grid")
+ assert_equal(expected, result)
+
+
+def test_grid_multiline_with_empty_cells_headerless():
+ "Output: grid with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "+---+----------------+------+",
+ "| 0 | | |",
+ "+---+----------------+------+",
+ "| 1 | | |",
+ "+---+----------------+------+",
+ "| 2 | very long data | fold |",
+ "| | | this |",
+ "+---+----------------+------+"])
+ result = tabulate(table, tablefmt="grid")
+ assert_equal(expected, result)
+
+
def test_fancy_grid():
"Output: fancy_grid with headers"
expected = '\n'.join([
@@ -103,6 +333,78 @@ def test_fancy_grid_headerless(): assert_equal(expected, result)
+def test_fancy_grid_multiline_headerless():
+ "Output: fancy_grid with multiline cells without headers"
+ table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+ expected = "\n".join([
+ "╒═════════╤═══════════╕",
+ "│ foo bar │ hello │",
+ "│ baz │ │",
+ "│ bau │ │",
+ "├─────────┼───────────┤",
+ "│ │ multiline │",
+ "│ │ world │",
+ "╘═════════╧═══════════╛"])
+ result = tabulate(table, stralign="center", tablefmt="fancy_grid")
+ assert_equal(expected, result)
+
+
+def test_fancy_grid_multiline():
+ "Output: fancy_grid with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ "╒═════════════╤═════════════╕",
+ "│ more │ more spam │",
+ "│ spam \x1b[31meggs\x1b[0m │ & eggs │",
+ "╞═════════════╪═════════════╡",
+ "│ 2 │ foo │",
+ "│ │ bar │",
+ "╘═════════════╧═════════════╛"])
+ result = tabulate(table, headers, tablefmt="fancy_grid")
+ assert_equal(expected, result)
+
+
+def test_fancy_grid_multiline_with_empty_cells():
+ "Output: fancy_grid with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "╒═══════╤════════════════╤════════╕",
+ "│ hdr │ data │ fold │",
+ "╞═══════╪════════════════╪════════╡",
+ "│ 1 │ │ │",
+ "├───────┼────────────────┼────────┤",
+ "│ 2 │ very long data │ fold │",
+ "│ │ │ this │",
+ "╘═══════╧════════════════╧════════╛"])
+ result = tabulate(table, headers="firstrow", tablefmt="fancy_grid")
+ assert_equal(expected, result)
+
+
+def test_fancy_grid_multiline_with_empty_cells_headerless():
+ "Output: fancy_grid with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "╒═══╤════════════════╤══════╕",
+ "│ 0 │ │ │",
+ "├───┼────────────────┼──────┤",
+ "│ 1 │ │ │",
+ "├───┼────────────────┼──────┤",
+ "│ 2 │ very long data │ fold │",
+ "│ │ │ this │",
+ "╘═══╧════════════════╧══════╛"])
+ result = tabulate(table, tablefmt="fancy_grid")
+ assert_equal(expected, result)
+
+
def test_pipe():
"Output: pipe with headers"
expected = '\n'.join(['| strings | numbers |',
@@ -122,6 +424,84 @@ def test_pipe_headerless(): assert_equal(expected, result)
+def test_presto():
+ "Output: presto with headers"
+ expected = '\n'.join([' strings | numbers',
+ '-----------+-----------',
+ ' spam | 41.9999',
+ ' eggs | 451',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="presto")
+ assert_equal(expected, result)
+
+
+def test_presto_headerless():
+ "Output: presto without headers"
+ expected = '\n'.join([' spam | 41.9999',
+ ' eggs | 451',])
+ result = tabulate(_test_table, tablefmt="presto")
+ assert_equal(expected, result)
+
+
+def test_presto_multiline_headerless():
+ "Output: presto with multiline cells without headers"
+ table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+ expected = "\n".join([
+ " foo bar | hello",
+ " baz |",
+ " bau |",
+ " | multiline",
+ " | world"])
+ result = tabulate(table, stralign="center", tablefmt="presto")
+ assert_equal(expected, result)
+
+
+def test_presto_multiline():
+ "Output: presto with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ " more | more spam",
+ " spam \x1b[31meggs\x1b[0m | & eggs",
+ "-------------+-------------",
+ " 2 | foo",
+ " | bar"])
+ result = tabulate(table, headers, tablefmt="presto")
+ assert_equal(expected, result)
+
+
+def test_presto_multiline_with_empty_cells():
+ "Output: presto with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ " hdr | data | fold",
+ "-------+----------------+--------",
+ " 1 | |",
+ " 2 | very long data | fold",
+ " | | this"])
+ result = tabulate(table, headers="firstrow", tablefmt="presto")
+ assert_equal(expected, result)
+
+
+def test_presto_multiline_with_empty_cells_headerless():
+ "Output: presto with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ " 0 | |",
+ " 1 | |",
+ " 2 | very long data | fold",
+ " | | this"])
+ result = tabulate(table, tablefmt="presto")
+ assert_equal(expected, result)
+
+
def test_orgtbl():
"Output: orgtbl with headers"
expected = '\n'.join(['| strings | numbers |',
@@ -159,6 +539,74 @@ def test_psql_headerless(): result = tabulate(_test_table, tablefmt="psql")
assert_equal(expected, result)
+def test_psql_multiline_headerless():
+ "Output: psql with multiline cells without headers"
+ table = [["foo bar\nbaz\nbau", "hello"], ["", "multiline\nworld"]]
+ expected = "\n".join([
+ "+---------+-----------+",
+ "| foo bar | hello |",
+ "| baz | |",
+ "| bau | |",
+ "| | multiline |",
+ "| | world |",
+ "+---------+-----------+"])
+ result = tabulate(table, stralign="center", tablefmt="psql")
+ assert_equal(expected, result)
+
+
+def test_psql_multiline():
+ "Output: psql with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ "+-------------+-------------+",
+ "| more | more spam |",
+ "| spam \x1b[31meggs\x1b[0m | & eggs |",
+ "|-------------+-------------|",
+ "| 2 | foo |",
+ "| | bar |",
+ "+-------------+-------------+"])
+ result = tabulate(table, headers, tablefmt="psql")
+ assert_equal(expected, result)
+
+
+def test_psql_multiline_with_empty_cells():
+ "Output: psql with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "+-------+----------------+--------+",
+ "| hdr | data | fold |",
+ "|-------+----------------+--------|",
+ "| 1 | | |",
+ "| 2 | very long data | fold |",
+ "| | | this |",
+ "+-------+----------------+--------+"])
+ result = tabulate(table, headers="firstrow", tablefmt="psql")
+ assert_equal(expected, result)
+
+
+def test_psql_multiline_with_empty_cells_headerless():
+ "Output: psql with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "+---+----------------+------+",
+ "| 0 | | |",
+ "| 1 | | |",
+ "| 2 | very long data | fold |",
+ "| | | this |",
+ "+---+----------------+------+"])
+ result = tabulate(table, tablefmt="psql")
+ assert_equal(expected, result)
+
+
def test_jira():
"Output: jira with headers"
expected = '\n'.join(['|| strings || numbers ||',
@@ -187,6 +635,18 @@ def test_rst(): result = tabulate(_test_table, _test_table_headers, tablefmt="rst")
assert_equal(expected, result)
+def test_rst_with_empty_values_in_first_column():
+ "Output: rst with dots in first column"
+ test_headers = ['', 'what']
+ test_data = [('', 'spam'), ('', 'eggs')]
+ expected = '\n'.join(['==== ======',
+ '.. what',
+ '==== ======',
+ '.. spam',
+ '.. eggs',
+ '==== ======'])
+ result = tabulate(test_data, test_headers, tablefmt="rst")
+ assert_equal(expected, result)
def test_rst_headerless():
"Output: rst without headers"
@@ -197,6 +657,59 @@ def test_rst_headerless(): result = tabulate(_test_table, tablefmt="rst")
assert_equal(expected, result)
+def test_rst_multiline():
+ "Output: rst with multiline cells with headers"
+ table = [[2, "foo\nbar"]]
+ headers = ("more\nspam \x1b[31meggs\x1b[0m", "more spam\n& eggs")
+ expected = "\n".join([
+ "=========== ===========",
+ " more more spam",
+ " spam \x1b[31meggs\x1b[0m & eggs",
+ "=========== ===========",
+ " 2 foo",
+ " bar",
+ "=========== ==========="])
+ result = tabulate(table, headers, tablefmt="rst")
+ assert_equal(expected, result)
+
+
+def test_rst_multiline_with_empty_cells():
+ "Output: rst with multiline cells and empty cells with headers"
+ table = [
+ ['hdr', 'data', 'fold'],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "===== ============== ======",
+ " hdr data fold",
+ "===== ============== ======",
+ " 1",
+ " 2 very long data fold",
+ " this",
+ "===== ============== ======"])
+ result = tabulate(table, headers="firstrow", tablefmt="rst")
+ assert_equal(expected, result)
+
+
+def test_rst_multiline_with_empty_cells_headerless():
+ "Output: rst with multiline cells and empty cells without headers"
+ table = [
+ ['0', '', ''],
+ ['1', '', ''],
+ ['2', 'very long data', 'fold\nthis']
+ ]
+ expected = "\n".join([
+ "= ============== ====",
+ "0",
+ "1",
+ "2 very long data fold",
+ " this",
+ "= ============== ===="])
+ result = tabulate(table, tablefmt="rst")
+ assert_equal(expected, result)
+
+
def test_mediawiki():
"Output: mediawiki with headers"
expected = '\n'.join(['{| class="wikitable" style="text-align: left;"',
@@ -232,6 +745,15 @@ def test_moinmoin(): result = tabulate(_test_table, _test_table_headers, tablefmt="moinmoin")
assert_equal(expected, result)
+def test_youtrack():
+ "Output: youtrack with headers"
+ expected = "\n".join(['|| strings || numbers ||',
+ '| spam | 41.9999 |',
+ '| eggs | 451 |',])
+ result = tabulate(_test_table, _test_table_headers, tablefmt="youtrack")
+ assert_equal(expected, result)
+
+
def test_moinmoin_headerless():
"Output: moinmoin without headers"
expected = "\n".join(['|| spam ||<style="text-align: right;"> 41.9999 ||',
@@ -270,18 +792,38 @@ def test_html_headerless(): def test_latex():
- "Output: latex with headers"
- result = tabulate(_test_table, _test_table_headers, tablefmt="latex")
+ "Output: latex with headers and replaced characters"
+ raw_test_table_headers = list(_test_table_headers)
+ raw_test_table_headers[-1] += " ($N_0$)"
+ result = tabulate(_test_table, raw_test_table_headers, tablefmt="latex")
expected = "\n".join([r"\begin{tabular}{lr}",
r"\hline",
- r" strings & numbers \\",
+ r" strings & numbers (\$N\_0\$) \\",
r"\hline",
- r" spam & 41.9999 \\",
- r" eggs & 451 \\",
+ r" spam & 41.9999 \\",
+ r" eggs & 451 \\",
r"\hline",
r"\end{tabular}"])
assert_equal(expected, result)
+def test_latex_raw():
+ "Output: raw latex with headers"
+ raw_test_table_headers = list(_test_table_headers)
+ raw_test_table_headers[-1] += " ($N_0$)"
+ raw_test_table = list(map(list,_test_table))
+ raw_test_table[0][0] += "$_1$"
+ raw_test_table[1][0] = "\\emph{" + raw_test_table[1][0] + "}"
+ print(raw_test_table)
+ result = tabulate(raw_test_table, raw_test_table_headers, tablefmt="latex_raw")
+ expected = "\n".join([r"\begin{tabular}{lr}",
+ r"\hline",
+ r" strings & numbers ($N_0$) \\",
+ r"\hline",
+ r" spam$_1$ & 41.9999 \\",
+ r" \emph{eggs} & 451 \\",
+ r"\hline",
+ r"\end{tabular}"])
+ assert_equal(expected, result)
def test_latex_headerless():
"Output: latex without headers"
@@ -388,6 +930,34 @@ def test_floatfmt(): assert_equal(expected, result)
+def test_floatfmt_multi():
+ "Output: floating point format different for each column"
+ result = tabulate([[0.12345, 0.12345, 0.12345]], floatfmt=(".1f", ".3f"), tablefmt="plain")
+ expected = '0.1 0.123 0.12345'
+ assert_equal(expected, result)
+
+def test_float_conversions():
+ "Output: float format parsed"
+ test_headers = ["str", "bad_float", "just_float", 'with_inf', 'with_nan', 'neg_inf']
+ test_table = [
+ ["spam", 41.9999, "123.345", '12.2', 'nan', '0.123123'],
+ ["eggs", "451.0", 66.2222, 'inf', 123.1234, '-inf'],
+ ["asd", "437e6548", 1.234e2, float('inf'), float('nan'), 0.22e23]
+ ]
+ result = tabulate(test_table, test_headers, tablefmt="grid")
+ expected = "\n".join([
+ '+-------+-------------+--------------+------------+------------+-------------+',
+ '| str | bad_float | just_float | with_inf | with_nan | neg_inf |',
+ '+=======+=============+==============+============+============+=============+',
+ '| spam | 41.9999 | 123.345 | 12.2 | nan | 0.123123 |',
+ '+-------+-------------+--------------+------------+------------+-------------+',
+ '| eggs | 451.0 | 66.2222 | inf | 123.123 | -inf |',
+ '+-------+-------------+--------------+------------+------------+-------------+',
+ '| asd | 437e6548 | 123.4 | inf | nan | 2.2e+22 |',
+ '+-------+-------------+--------------+------------+------------+-------------+'
+ ])
+ assert_equal(expected, result)
+
def test_missingval():
"Output: substitution of missing values"
result = tabulate([['Alice', 10],['Bob', None]], missingval="n/a", tablefmt="plain")
@@ -395,6 +965,14 @@ def test_missingval(): assert_equal(expected, result)
+def test_missingval_multi():
+ "Output: substitution of missing values with different values per column"
+ result = tabulate([["Alice", "Bob", "Charlie"], [None, None, None]],
+ missingval=("n/a", "?"), tablefmt="plain")
+ expected = 'Alice Bob Charlie\nn/a ?'
+ assert_equal(expected, result)
+
+
def test_column_alignment():
"Output: custom alignment for text and numbers"
expected = '\n'.join(['----- ---',
@@ -592,3 +1170,23 @@ def test_disable_numparse_list(): 'foo bar 429920',])
result = tabulate(test_table, table_headers, disable_numparse=[0, 1])
assert_equal(expected, result)
+
+def test_preserve_whitespace():
+ "Output: Default table output, but with preserved leading whitespace."
+ tabulate_module.PRESERVE_WHITESPACE = True
+ table_headers = ['h1', 'h2', 'h3']
+ test_table = [[' foo', ' bar ', 'foo']]
+ expected = "\n".join(['h1 h2 h3',
+ '----- ------- ----',
+ ' foo bar foo'])
+ result = tabulate(test_table, table_headers)
+ assert_equal(expected, result)
+
+ tabulate_module.PRESERVE_WHITESPACE = False
+ table_headers = ['h1', 'h2', 'h3']
+ test_table = [[' foo', ' bar ', 'foo']]
+ expected = "\n".join(['h1 h2 h3',
+ '---- ---- ----',
+ 'foo bar foo'])
+ result = tabulate(test_table, table_headers)
+ assert_equal(expected, result)
|
