Adding upstream version 3.1.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
96
.gitignore
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# IPython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Robpol86
|
||||
test*.png
|
||||
*.rpm
|
||||
.idea/
|
||||
requirements*.txt
|
||||
.DS_Store
|
48
.travis.yml
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Configure.
|
||||
env: TOX_ENV=py
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.5
|
||||
env: TOX_ENV=lint
|
||||
after_success:
|
||||
- echo
|
||||
- python: 3.5
|
||||
env: TOX_ENV=docs
|
||||
after_success:
|
||||
- eval "$(ssh-agent -s)"; touch docs/key; chmod 0600 docs/key
|
||||
- openssl aes-256-cbc -d -K "$encrypted_c89fed6a587d_key" -iv "$encrypted_c89fed6a587d_iv"
|
||||
< docs/key.enc > docs/key && ssh-add docs/key
|
||||
- git config --global user.email "builds@travis-ci.com"
|
||||
- git config --global user.name "Travis CI"
|
||||
- git remote set-url --push origin "git@github.com:$TRAVIS_REPO_SLUG"
|
||||
- export ${!TRAVIS*}
|
||||
- tox -e docsV
|
||||
python:
|
||||
- 3.5
|
||||
- 3.4
|
||||
- 3.3
|
||||
- pypy3
|
||||
- pypy
|
||||
- 2.7
|
||||
- 2.6
|
||||
sudo: false
|
||||
|
||||
# Run.
|
||||
install: pip install tox
|
||||
script: tox -e $TOX_ENV
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
# Deploy.
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: Robpol86
|
||||
password:
|
||||
secure:
|
||||
"aj+Hl25+NbtmKpHcqxxNJhaMmawgzEPdLX+NwxwAZuTrvUCdiMtYhF9qxN0USHIlXSGDNc\
|
||||
7ua6nNpYPhjRv7j5YM4uLlK+4Fv/iU+iQcVfy89BS4vlXzUoje6nLIhogsxytb+FjdGZ0PK\
|
||||
JzzxfYr0relUjui/gPYmTQoZ1IiT8A="
|
||||
on:
|
||||
condition: $TRAVIS_PYTHON_VERSION = 3.4
|
||||
tags: true
|
47
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Contributing
|
||||
|
||||
Everyone that wants to contribute to the project should read this document.
|
||||
|
||||
## Getting Started
|
||||
|
||||
You may follow these steps if you wish to create a pull request. Fork the repo and clone it on your local machine. Then
|
||||
in the project's directory:
|
||||
|
||||
```bash
|
||||
virtualenv env # Create a virtualenv for the project's dependencies.
|
||||
source env/bin/activate # Activate the virtualenv.
|
||||
pip install tox # Install tox, which runs linting and tests.
|
||||
tox # This runs all tests on your local machine. Make sure they pass.
|
||||
```
|
||||
|
||||
If you don't have Python 2.6, 2.7, or 3.4 installed you can manually run tests on one specific version by running
|
||||
`tox -e lint,py35` (for Python 3.5) instead.
|
||||
|
||||
## Updating Docs
|
||||
|
||||
You don't need to but if you wish to update the [Sphinx](http://sphinx-doc.org/) documentation for this project you can
|
||||
get started by running these commands:
|
||||
|
||||
```bash
|
||||
source env/bin/activate
|
||||
pip install tox
|
||||
tox -e docs
|
||||
open docs/_build/html/index.html # Opens this file in your browser.
|
||||
```
|
||||
|
||||
## Consistency and Style
|
||||
|
||||
Keep code style consistent with the rest of the project. Some suggestions:
|
||||
|
||||
1. **Write tests for your new features.** `if new_feature else` **Write tests for bug-causing scenarios.**
|
||||
2. Write docstrings for all classes, functions, methods, modules, etc.
|
||||
3. Document all function/method arguments and return values.
|
||||
4. Document all class variables instance variables.
|
||||
5. Documentation guidelines also apply to tests, though not as strict.
|
||||
6. Keep code style consistent, such as the kind of quotes to use and spacing.
|
||||
7. Don't use `except:` or `except Exception:` unless you have a `raise` in the block. Be specific about error handling.
|
||||
8. Don't use `isinstance()` (it breaks [duck typing](https://en.wikipedia.org/wiki/Duck_typing#In_Python)).
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks for fixing bugs or adding features to the project!
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Robpol86
|
||||
|
||||
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.
|
162
README.rst
Normal file
|
@ -0,0 +1,162 @@
|
|||
==============
|
||||
terminaltables
|
||||
==============
|
||||
|
||||
Easily draw tables in terminal/console applications from a list of lists of strings. Supports multi-line rows.
|
||||
|
||||
* Python 2.6, 2.7, PyPy, PyPy3, 3.3, 3.4, and 3.5 supported on Linux and OS X.
|
||||
* Python 2.7, 3.3, 3.4, and 3.5 supported on Windows (both 32 and 64 bit versions of Python).
|
||||
|
||||
📖 Full documentation: https://robpol86.github.io/terminaltables
|
||||
|
||||
.. image:: https://img.shields.io/appveyor/ci/Robpol86/terminaltables/master.svg?style=flat-square&label=AppVeyor%20CI
|
||||
:target: https://ci.appveyor.com/project/Robpol86/terminaltables
|
||||
:alt: Build Status Windows
|
||||
|
||||
.. image:: https://img.shields.io/travis/Robpol86/terminaltables/master.svg?style=flat-square&label=Travis%20CI
|
||||
:target: https://travis-ci.org/Robpol86/terminaltables
|
||||
:alt: Build Status
|
||||
|
||||
.. image:: https://img.shields.io/codecov/c/github/Robpol86/terminaltables/master.svg?style=flat-square&label=Codecov
|
||||
:target: https://codecov.io/gh/Robpol86/terminaltables
|
||||
:alt: Coverage Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/terminaltables.svg?style=flat-square&label=Latest
|
||||
:target: https://pypi.python.org/pypi/terminaltables
|
||||
:alt: Latest Version
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
Install:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
pip install terminaltables
|
||||
|
||||
Usage:
|
||||
|
||||
.. code::
|
||||
|
||||
from terminaltables import AsciiTable
|
||||
table_data = [
|
||||
['Heading1', 'Heading2'],
|
||||
['row1 column1', 'row1 column2'],
|
||||
['row2 column1', 'row2 column2'],
|
||||
['row3 column1', 'row3 column2']
|
||||
]
|
||||
table = AsciiTable(table_data)
|
||||
print table.table
|
||||
+--------------+--------------+
|
||||
| Heading1 | Heading2 |
|
||||
+--------------+--------------+
|
||||
| row1 column1 | row1 column2 |
|
||||
| row2 column1 | row2 column2 |
|
||||
| row3 column1 | row3 column2 |
|
||||
+--------------+--------------+
|
||||
|
||||
Example Implementations
|
||||
=======================
|
||||
|
||||
.. image:: docs/examples.png?raw=true
|
||||
:alt: Example Scripts Screenshot
|
||||
|
||||
Source code for examples: `example1.py <https://github.com/Robpol86/terminaltables/blob/master/example1.py>`_,
|
||||
`example2.py <https://github.com/Robpol86/terminaltables/blob/master/example2.py>`_, and
|
||||
`example3.py <https://github.com/Robpol86/terminaltables/blob/master/example3.py>`_
|
||||
|
||||
.. changelog-section-start
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
This project adheres to `Semantic Versioning <http://semver.org/>`_.
|
||||
|
||||
3.1.0 - 2016-10-16
|
||||
------------------
|
||||
|
||||
Added
|
||||
* ``git --porcelain``-like table by liiight: https://github.com/Robpol86/terminaltables/pull/31
|
||||
|
||||
3.0.0 - 2016-05-30
|
||||
------------------
|
||||
|
||||
Added
|
||||
* Support for https://pypi.python.org/pypi/colorama
|
||||
* Support for https://pypi.python.org/pypi/termcolor
|
||||
* Support for RTL characters (Arabic and Hebrew).
|
||||
* Support for non-string items in ``table_data`` like integers.
|
||||
|
||||
Changed
|
||||
* Refactored again, but this time entire project including tests.
|
||||
|
||||
Removed
|
||||
* ``padded_table_data`` property and ``join_row()``. Moving away from repeated string joining/splitting.
|
||||
|
||||
Fixed
|
||||
* ``set_terminal_title()`` Unicode handling on Windows.
|
||||
* https://github.com/Robpol86/terminaltables/issues/18
|
||||
* https://github.com/Robpol86/terminaltables/issues/20
|
||||
* https://github.com/Robpol86/terminaltables/issues/23
|
||||
* https://github.com/Robpol86/terminaltables/issues/26
|
||||
|
||||
2.1.0 - 2015-11-02
|
||||
------------------
|
||||
|
||||
Added
|
||||
* GitHub Flavored Markdown table by bcho: https://github.com/Robpol86/terminaltables/pull/12
|
||||
* Python 3.5 support (Linux/OS X and Windows).
|
||||
|
||||
2.0.0 - 2015-10-11
|
||||
------------------
|
||||
|
||||
Changed
|
||||
* Refactored code. No new features.
|
||||
* Breaking changes: ``UnixTable``/``WindowsTable``/``WindowsTableDouble`` moved. Use ``SingleTable``/``DoubleTable``
|
||||
instead.
|
||||
|
||||
1.2.1 - 2015-09-03
|
||||
------------------
|
||||
|
||||
Fixed
|
||||
* CJK character width fixed by zqqf16 and bcho: https://github.com/Robpol86/terminaltables/pull/9
|
||||
|
||||
1.2.0 - 2015-05-31
|
||||
------------------
|
||||
|
||||
Added
|
||||
* Bottom row separator.
|
||||
|
||||
1.1.1 - 2014-11-03
|
||||
------------------
|
||||
|
||||
Fixed
|
||||
* Python 2.7 64-bit terminal width bug on Windows.
|
||||
|
||||
1.1.0 - 2014-11-02
|
||||
------------------
|
||||
|
||||
Added
|
||||
* Windows support.
|
||||
* Double-lined table.
|
||||
|
||||
1.0.2 - 2014-09-18
|
||||
------------------
|
||||
|
||||
Added
|
||||
* ``table_width`` and ``ok`` properties.
|
||||
|
||||
1.0.1 - 2014-09-12
|
||||
------------------
|
||||
|
||||
Added
|
||||
* Terminal width/height defaults for testing.
|
||||
* ``terminaltables.DEFAULT_TERMINAL_WIDTH``
|
||||
* ``terminaltables.DEFAULT_TERMINAL_HEIGHT``
|
||||
|
||||
1.0.0 - 2014-09-11
|
||||
------------------
|
||||
|
||||
* Initial release.
|
||||
|
||||
.. changelog-section-end
|
33
appveyor.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Configure.
|
||||
environment:
|
||||
PYTHON: Python35
|
||||
matrix:
|
||||
- TOX_ENV: lint
|
||||
- TOX_ENV: py35
|
||||
- TOX_ENV: py34
|
||||
- TOX_ENV: py33
|
||||
- TOX_ENV: py27
|
||||
- TOX_ENV: py
|
||||
PYTHON: Python35-x64
|
||||
- TOX_ENV: py
|
||||
PYTHON: Python34-x64
|
||||
- TOX_ENV: py
|
||||
PYTHON: Python33-x64
|
||||
- TOX_ENV: py
|
||||
PYTHON: Python27-x64
|
||||
|
||||
# Run.
|
||||
init: set PATH=C:\%PYTHON%;C:\%PYTHON%\Scripts;%PATH%
|
||||
install:
|
||||
- appveyor DownloadFile https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-desktop.ps1
|
||||
- ps: .\enable-desktop
|
||||
build_script: pip install tox
|
||||
test_script: tox -e %TOX_ENV%
|
||||
on_success: IF %TOX_ENV% NEQ lint pip install codecov & codecov
|
||||
|
||||
# Post.
|
||||
# on_finish: https://github.com/Robpol86/terminaltables/issues/30
|
||||
#- appveyor PushArtifact test_ascii_table.png https://github.com/Robpol86/terminaltables/issues/30
|
||||
#- appveyor PushArtifact test_double_table.png https://github.com/Robpol86/terminaltables/issues/30
|
||||
#- appveyor PushArtifact test_single_table.png https://github.com/Robpol86/terminaltables/issues/30
|
||||
#- appveyor PushArtifact test_terminal_io.png https://github.com/Robpol86/terminaltables/issues/30
|
6
docs/_templates/layout.html
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{# From https://github.com/snide/sphinx_rtd_theme/issues/166 #}
|
||||
|
||||
{# Import the theme's layout. #}
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% set css_files = css_files + ['_static/pygments.css'] %}
|
BIN
docs/asciitable.png
Normal file
After Width: | Height: | Size: 44 KiB |
16
docs/asciitable.rst
Normal file
|
@ -0,0 +1,16 @@
|
|||
.. _asciitable:
|
||||
|
||||
==========
|
||||
AsciiTable
|
||||
==========
|
||||
|
||||
AsciiTable is the simplest table. It uses ``+``, ``|``, and ``-`` characters to build the borders.
|
||||
|
||||
.. image:: asciitable.png
|
||||
:target: _images/asciitable.png
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. autoclass:: terminaltables.AsciiTable
|
||||
:members: column_max_width, column_widths, ok, table_width, table
|
5
docs/changelog.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
.. _changelog:
|
||||
|
||||
.. include:: ../README.rst
|
||||
:start-after: changelog-section-start
|
||||
:end-before: changelog-section-end
|
53
docs/conf.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
"""Sphinx configuration file."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
# General configuration.
|
||||
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
author = '@Robpol86'
|
||||
copyright = '{}, {}'.format(time.strftime('%Y'), author)
|
||||
master_doc = 'index'
|
||||
project = __import__('setup').NAME
|
||||
pygments_style = 'friendly'
|
||||
release = version = __import__('setup').VERSION
|
||||
templates_path = ['_templates']
|
||||
extensions = list()
|
||||
|
||||
|
||||
# Options for HTML output.
|
||||
html_context = dict(
|
||||
conf_py_path='/docs/',
|
||||
display_github=True,
|
||||
github_repo=os.environ.get('TRAVIS_REPO_SLUG', '/' + project).split('/', 1)[1],
|
||||
github_user=os.environ.get('TRAVIS_REPO_SLUG', 'robpol86/').split('/', 1)[0],
|
||||
github_version=os.environ.get('TRAVIS_BRANCH', 'master'),
|
||||
source_suffix='.rst',
|
||||
)
|
||||
html_copy_source = False
|
||||
html_favicon = 'favicon.ico'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_title = project
|
||||
|
||||
|
||||
# autodoc
|
||||
extensions.append('sphinx.ext.autodoc')
|
||||
|
||||
|
||||
# extlinks.
|
||||
extensions.append('sphinx.ext.extlinks')
|
||||
extlinks = {'github': ('https://github.com/robpol86/{0}/blob/v{1}/%s'.format(project, version), '')}
|
||||
|
||||
|
||||
# google analytics
|
||||
extensions.append('sphinxcontrib.googleanalytics')
|
||||
googleanalytics_id = 'UA-82627369-1'
|
||||
|
||||
|
||||
# SCVersioning.
|
||||
scv_banner_greatest_tag = True
|
||||
scv_grm_exclude = ('.gitignore', '.nojekyll', 'README.rst')
|
||||
scv_show_banner = True
|
||||
scv_sort = ('semver', 'time')
|
BIN
docs/doubletable.png
Normal file
After Width: | Height: | Size: 45 KiB |
33
docs/doubletable.rst
Normal file
|
@ -0,0 +1,33 @@
|
|||
.. _doubletable:
|
||||
|
||||
===========
|
||||
DoubleTable
|
||||
===========
|
||||
|
||||
DoubleTable uses `box drawing characters`_ for table borders. On Windows terminaltables uses `code page 437`_
|
||||
characters. However there is no equivalent character set for POSIX (Linux/OS X). Python automatically converts CP437
|
||||
double-line box characters to Unicode and displays that instead.
|
||||
|
||||
.. image:: doubletable.png
|
||||
:target: _images/doubletable.png
|
||||
|
||||
Gaps on Windows 10
|
||||
==================
|
||||
|
||||
Like SingleTable the console on Windows 10 changed the default font face to ``Consolas``. This new font seems to show
|
||||
gaps between lines. Switching the font back to ``Lucida Console`` eliminates the gaps.
|
||||
|
||||
Gaps on POSIX
|
||||
=============
|
||||
|
||||
There is no easy trick for POSIX like there is on Windows. I can't seem to find out how to force terminals to eliminate
|
||||
gaps vertically between Unicode characters.
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. autoclass:: terminaltables.DoubleTable
|
||||
:members: column_max_width, column_widths, ok, table_width, table
|
||||
|
||||
.. _box drawing characters: https://en.wikipedia.org/wiki/Box-drawing_character
|
||||
.. _code page 437: https://en.wikipedia.org/wiki/Code_page_437
|
BIN
docs/examples.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
docs/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/githubtable.png
Normal file
After Width: | Height: | Size: 39 KiB |
27
docs/githubtable.rst
Normal file
|
@ -0,0 +1,27 @@
|
|||
.. _githubtable:
|
||||
|
||||
===========================
|
||||
GithubFlavoredMarkdownTable
|
||||
===========================
|
||||
|
||||
GithubFlavoredMarkdownTable was initially implemented bcho_. It produces a `GitHub Flavored Markdown`_ formatted table.
|
||||
|
||||
Because there are no outer table borders:
|
||||
|
||||
* Table titles are ignored.
|
||||
* Border display toggles are also ignored.
|
||||
|
||||
.. image:: githubtable.png
|
||||
:target: _images/githubtable.png
|
||||
|
||||
.. image:: githubtable_rendered.png
|
||||
:target: _images/githubtable_rendered.png
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. autoclass:: terminaltables.GithubFlavoredMarkdownTable
|
||||
:members: column_max_width, column_widths, ok, table_width, table
|
||||
|
||||
.. _bcho: https://github.com/Robpol86/terminaltables/pull/12
|
||||
.. _GitHub Flavored Markdown: https://help.github.com/categories/writing-on-github
|
BIN
docs/githubtable_rendered.png
Normal file
After Width: | Height: | Size: 53 KiB |
77
docs/index.rst
Normal file
|
@ -0,0 +1,77 @@
|
|||
========================
|
||||
terminaltables |version|
|
||||
========================
|
||||
|
||||
Easily draw tables in terminal/console applications from a list of lists of strings. As easy as:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from terminaltables import AsciiTable
|
||||
>>> table_data = [
|
||||
... ['Heading1', 'Heading2'],
|
||||
... ['row1 column1', 'row1 column2'],
|
||||
... ['row2 column1', 'row2 column2'],
|
||||
... ['row3 column1', 'row3 column2'],
|
||||
... ]
|
||||
>>> table = AsciiTable(table_data)
|
||||
>>> print table.table
|
||||
+--------------+--------------+
|
||||
| Heading1 | Heading2 |
|
||||
+--------------+--------------+
|
||||
| row1 column1 | row1 column2 |
|
||||
| row2 column1 | row2 column2 |
|
||||
| row3 column1 | row3 column2 |
|
||||
+--------------+--------------+
|
||||
|
||||
.. figure:: examples.png
|
||||
:target: _images/examples.png
|
||||
|
||||
Windows 10, Windows XP, and OS X are also supported. View source: :github:`example1.py`, :github:`example2.py`,
|
||||
:github:`example3.py`
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
* Multi-line rows: add newlines to table cells and terminatables will handle the rest.
|
||||
* Table titles: show a title embedded in the top border of the table.
|
||||
* POSIX: Python 2.6, 2.7, PyPy, PyPy3, 3.3, 3.4, and 3.5 supported on Linux and OS X.
|
||||
* Windows: Python 2.7, 3.3, 3.4, and 3.5 supported on Windows XP through 10.
|
||||
* CJK: Wide Chinese/Japanese/Korean characters displayed correctly.
|
||||
* RTL: Arabic and Hebrew characters aligned correctly.
|
||||
* Alignment/Justification: Align individual columns left, center, or right.
|
||||
* Colored text: colorclass_, colorama_, termcolor_, or just plain `ANSI escape codes`_.
|
||||
|
||||
Project Links
|
||||
=============
|
||||
|
||||
* Documentation: https://robpol86.github.io/terminaltables
|
||||
* Source code: https://github.com/Robpol86/terminaltables
|
||||
* PyPI homepage: https://pypi.python.org/pypi/terminaltables
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: General
|
||||
|
||||
install
|
||||
quickstart
|
||||
settings
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Table Styles
|
||||
|
||||
asciitable
|
||||
singletable
|
||||
doubletable
|
||||
githubtable
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Appendix
|
||||
|
||||
changelog
|
||||
|
||||
.. _colorclass: https://github.com/Robpol86/colorclass
|
||||
.. _colorama: https://github.com/tartley/colorama
|
||||
.. _termcolor: https://pypi.python.org/pypi/termcolor
|
||||
.. _ANSI escape codes: http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
|
38
docs/install.rst
Normal file
|
@ -0,0 +1,38 @@
|
|||
.. _install:
|
||||
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
Getting started is pretty simple. The first step is to install the library.
|
||||
|
||||
Pip Install
|
||||
===========
|
||||
|
||||
The easiest way to get terminaltables is to use `pip <https://pip.pypa.io>`_. Simply run this command.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install terminaltables
|
||||
|
||||
Latest from GitHub
|
||||
==================
|
||||
|
||||
You can also elect to install the latest bleeding-edge version by using pip to install directly from the GitHub
|
||||
repository.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install git+https://github.com/Robpol86/terminaltables.git
|
||||
|
||||
Clone and Install
|
||||
=================
|
||||
|
||||
Lastly you can also just clone the repo and install from it. Usually you only need to do this if you plan on
|
||||
`contributing <https://github.com/Robpol86/terminaltables/blob/master/CONTRIBUTING.md>`_ to the project.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/Robpol86/terminaltables.git
|
||||
cd terminaltables
|
||||
python setup.py install
|
BIN
docs/key.enc
Normal file
110
docs/quickstart.rst
Normal file
|
@ -0,0 +1,110 @@
|
|||
.. _quickstart:
|
||||
|
||||
==========
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
This section will go over the basics of terminaltables.
|
||||
|
||||
Make sure that you've already :ref:`installed <install>` it.
|
||||
|
||||
Table with Default Settings
|
||||
===========================
|
||||
|
||||
Let's begin by importing AsciiTable, which just uses ``+``, ``-``, and ``|`` characters.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from terminaltables import AsciiTable
|
||||
|
||||
Now let's define the table data in a variable called ``data``. We'll do it the long way by creating an empty list
|
||||
representing the entire table. Then we'll add rows one by one. Each row is a list representing table cells.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> data = []
|
||||
>>> data.append(['Row one column one', 'Row one column two'])
|
||||
>>> data.append(['Row two column one', 'Row two column two'])
|
||||
>>> data.append(['Row three column one', 'Row three column two'])
|
||||
|
||||
Next we can use AsciiTable to format the table properly and then we can just print it. ``table.table`` gives you just
|
||||
one long string with newline characters so you can easily print it.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> table = AsciiTable(data)
|
||||
>>> print table.table
|
||||
+----------------------+----------------------+
|
||||
| Row one column one | Row one column two |
|
||||
+----------------------+----------------------+
|
||||
| Row two column one | Row two column two |
|
||||
| Row three column one | Row three column two |
|
||||
+----------------------+----------------------+
|
||||
|
||||
By default the first row of the table is considered the heading. This can be turned off.
|
||||
|
||||
Changing Table Settings
|
||||
=======================
|
||||
|
||||
There are more options available to change how your tables are formatted. Say your table doesn't really have a heading
|
||||
row; all rows are just data.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> table.inner_heading_row_border = False
|
||||
>>> print table.table
|
||||
+----------------------+----------------------+
|
||||
| Row one column one | Row one column two |
|
||||
| Row two column one | Row two column two |
|
||||
| Row three column one | Row three column two |
|
||||
+----------------------+----------------------+
|
||||
|
||||
Now you want to add a title to the table:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> table.title = 'My Table'
|
||||
>>> print table.table
|
||||
+My Table--------------+----------------------+
|
||||
| Row one column one | Row one column two |
|
||||
| Row two column one | Row two column two |
|
||||
| Row three column one | Row three column two |
|
||||
+----------------------+----------------------+
|
||||
|
||||
Maybe you want lines in between all rows:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> table.inner_row_border = True
|
||||
>>> print table.table
|
||||
+My Table--------------+----------------------+
|
||||
| Row one column one | Row one column two |
|
||||
+----------------------+----------------------+
|
||||
| Row two column one | Row two column two |
|
||||
+----------------------+----------------------+
|
||||
| Row three column one | Row three column two |
|
||||
+----------------------+----------------------+
|
||||
|
||||
There are many more settings available. You can find out more by reading the :ref:`settings` section. Each table style
|
||||
pretty much shares the same settings but there are a few minor exceptions. Refer to each table style's documentation on
|
||||
the sidebar.
|
||||
|
||||
Other Table Styles
|
||||
==================
|
||||
|
||||
Terminaltables comes with a few other table styles than just ``AsciiTable``. All table styles more or less have the same
|
||||
API.
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from terminaltables import SingleTable
|
||||
>>> table = SingleTable(data)
|
||||
>>> print table.table
|
||||
┌──────────────────────┬──────────────────────┐
|
||||
│ Row one column one │ Row one column two │
|
||||
├──────────────────────┼──────────────────────┤
|
||||
│ Row two column one │ Row two column two │
|
||||
│ Row three column one │ Row three column two │
|
||||
└──────────────────────┴──────────────────────┘
|
||||
|
||||
You can find documentation for all table styles on the sidebar.
|
79
docs/settings.rst
Normal file
|
@ -0,0 +1,79 @@
|
|||
.. _settings:
|
||||
|
||||
========
|
||||
Settings
|
||||
========
|
||||
|
||||
All tables (except :ref:`githubtable`) have the same settings to change the way the table is displayed. These attributes
|
||||
are available after instantiation.
|
||||
|
||||
.. py:attribute:: Table.table_data
|
||||
|
||||
The actual table data to render. This must be a list (or tuple) of lists of strings. The outer list holds the rows
|
||||
and the inner lists holds the cells (aka columns in that row).
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
table.table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
|
||||
.. py:attribute:: Table.title
|
||||
|
||||
Optional title to show within the top border of the table. This is ignored if None or a blank string.
|
||||
|
||||
.. py:attribute:: Table.inner_column_border
|
||||
|
||||
Toggles the column dividers. Set to **False** to disable these vertically dividing borders.
|
||||
|
||||
.. py:attribute:: Table.inner_footing_row_border
|
||||
|
||||
Show a horizontal dividing border before the last row. If **True** this defines the last row as the table footer.
|
||||
|
||||
.. py:attribute:: Table.inner_heading_row_border
|
||||
|
||||
Show a horizontal dividing border after the first row. If **False** this removes the border so the first row is no
|
||||
longer considered a header row. It'll look just like any other row.
|
||||
|
||||
.. py:attribute:: Table.inner_row_border
|
||||
|
||||
If **True** terminaltables will show dividing borders between every row.
|
||||
|
||||
.. py:attribute:: Table.outer_border
|
||||
|
||||
Toggles the four outer borders. If **False** the top, left, right, and bottom borders will not be shown.
|
||||
|
||||
.. py:attribute:: Table.justify_columns
|
||||
|
||||
Aligns text in entire columns. The keys in this dict are column integers (0 for the first column) and the values
|
||||
are either 'left', 'right', or 'center'. Left is the default.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> table.justify_columns[0] = 'right' # Name column.
|
||||
>>> table.justify_columns[1] = 'center' # Color column.
|
||||
>>> print table.table
|
||||
+---------+-------+-----------+
|
||||
| Name | Color | Type |
|
||||
+---------+-------+-----------+
|
||||
| Avocado | green | nut |
|
||||
| Tomato | red | fruit |
|
||||
| Lettuce | green | vegetable |
|
||||
+---------+-------+-----------+
|
||||
|
||||
.. py:attribute:: Table.padding_left
|
||||
|
||||
Number of spaces to pad on the left side of every cell. Default is **1**. Padding adds spacing between the cell text
|
||||
and the column border.
|
||||
|
||||
.. py:attribute:: Table.padding_right
|
||||
|
||||
Number of spaces to pad on the right side of every cell. Default is **1**. Padding adds spacing between the cell
|
||||
text and the column border.
|
BIN
docs/singletable.png
Normal file
After Width: | Height: | Size: 44 KiB |
26
docs/singletable.rst
Normal file
|
@ -0,0 +1,26 @@
|
|||
.. _singletable:
|
||||
|
||||
===========
|
||||
SingleTable
|
||||
===========
|
||||
|
||||
SingleTable uses `box drawing characters`_ for table borders. On POSIX (Linux/OS X) terminaltables uses ``Esc ( 0``
|
||||
characters while on Windows it uses `code page 437`_ characters.
|
||||
|
||||
.. image:: singletable.png
|
||||
:target: _images/singletable.png
|
||||
|
||||
Gaps on Windows 10
|
||||
==================
|
||||
|
||||
Unfortunately the console on Windows 10 changed the default font face to ``Consolas``. This new font seems to show gaps
|
||||
between lines. Switching the font back to ``Lucida Console`` eliminates the gaps.
|
||||
|
||||
API
|
||||
===
|
||||
|
||||
.. autoclass:: terminaltables.SingleTable
|
||||
:members: column_max_width, column_widths, ok, table_width, table
|
||||
|
||||
.. _box drawing characters: https://en.wikipedia.org/wiki/Box-drawing_character
|
||||
.. _code page 437: https://en.wikipedia.org/wiki/Code_page_437
|
42
example1.py
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
"""Simple example usage of terminaltables without any other dependencies.
|
||||
|
||||
Just prints sample text and exits.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from terminaltables import AsciiTable, DoubleTable, SingleTable
|
||||
|
||||
TABLE_DATA = (
|
||||
('Platform', 'Years', 'Notes'),
|
||||
('Mk5', '2007-2009', 'The Golf Mk5 Variant was\nintroduced in 2007.'),
|
||||
('MKVI', '2009-2013', 'Might actually be Mk5.'),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
title = 'Jetta SportWagen'
|
||||
|
||||
# AsciiTable.
|
||||
table_instance = AsciiTable(TABLE_DATA, title)
|
||||
table_instance.justify_columns[2] = 'right'
|
||||
print(table_instance.table)
|
||||
print()
|
||||
|
||||
# SingleTable.
|
||||
table_instance = SingleTable(TABLE_DATA, title)
|
||||
table_instance.justify_columns[2] = 'right'
|
||||
print(table_instance.table)
|
||||
print()
|
||||
|
||||
# DoubleTable.
|
||||
table_instance = DoubleTable(TABLE_DATA, title)
|
||||
table_instance.justify_columns[2] = 'right'
|
||||
print(table_instance.table)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
86
example2.py
Executable file
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env python
|
||||
"""Example usage of terminaltables with colorclass.
|
||||
|
||||
Just prints sample text and exits.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from colorclass import Color, Windows
|
||||
|
||||
from terminaltables import SingleTable
|
||||
|
||||
|
||||
def table_server_timings():
|
||||
"""Return table string to be printed."""
|
||||
table_data = [
|
||||
[Color('{autogreen}<10ms{/autogreen}'), '192.168.0.100, 192.168.0.101'],
|
||||
[Color('{autoyellow}10ms <= 100ms{/autoyellow}'), '192.168.0.102, 192.168.0.103'],
|
||||
[Color('{autored}>100ms{/autored}'), '192.168.0.105'],
|
||||
]
|
||||
table_instance = SingleTable(table_data)
|
||||
table_instance.inner_heading_row_border = False
|
||||
return table_instance.table
|
||||
|
||||
|
||||
def table_server_status():
|
||||
"""Return table string to be printed."""
|
||||
table_data = [
|
||||
[Color('Low Space'), Color('{autocyan}Nominal Space{/autocyan}'), Color('Excessive Space')],
|
||||
[Color('Low Load'), Color('Nominal Load'), Color('{autored}High Load{/autored}')],
|
||||
[Color('{autocyan}Low Free RAM{/autocyan}'), Color('Nominal Free RAM'), Color('High Free RAM')],
|
||||
]
|
||||
table_instance = SingleTable(table_data, '192.168.0.105')
|
||||
table_instance.inner_heading_row_border = False
|
||||
table_instance.inner_row_border = True
|
||||
table_instance.justify_columns = {0: 'center', 1: 'center', 2: 'center'}
|
||||
return table_instance.table
|
||||
|
||||
|
||||
def table_abcd():
|
||||
"""Return table string to be printed. Two tables on one line."""
|
||||
table_instance = SingleTable([['A', 'B'], ['C', 'D']])
|
||||
|
||||
# Get first table lines.
|
||||
table_instance.outer_border = False
|
||||
table_inner_borders = table_instance.table.splitlines()
|
||||
|
||||
# Get second table lines.
|
||||
table_instance.outer_border = True
|
||||
table_instance.inner_heading_row_border = False
|
||||
table_instance.inner_column_border = False
|
||||
table_outer_borders = table_instance.table.splitlines()
|
||||
|
||||
# Combine.
|
||||
smallest, largest = sorted([table_inner_borders, table_outer_borders], key=len)
|
||||
smallest += [''] * (len(largest) - len(smallest)) # Make both same size.
|
||||
combined = list()
|
||||
for i, row in enumerate(largest):
|
||||
combined.append(row.ljust(10) + ' ' + smallest[i])
|
||||
return '\n'.join(combined)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
Windows.enable(auto_colors=True, reset_atexit=True) # Does nothing if not on Windows.
|
||||
|
||||
# Server timings.
|
||||
print(table_server_timings())
|
||||
print()
|
||||
|
||||
# Server status.
|
||||
print(table_server_status())
|
||||
print()
|
||||
|
||||
# Two A B C D tables.
|
||||
print(table_abcd())
|
||||
print()
|
||||
|
||||
# Instructions.
|
||||
table_instance = SingleTable([['Obey Obey Obey Obey']], 'Instructions')
|
||||
print(table_instance.table)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
36
example3.py
Executable file
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python
|
||||
"""Simple example usage of terminaltables and column_max_width().
|
||||
|
||||
Just prints sample text and exits.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from textwrap import wrap
|
||||
|
||||
from terminaltables import SingleTable
|
||||
|
||||
LONG_STRING = ('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore '
|
||||
'et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut '
|
||||
'aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum '
|
||||
'dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui '
|
||||
'officia deserunt mollit anim id est laborum.')
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
table_data = [
|
||||
['Long String', ''], # One row. Two columns. Long string will replace this empty string.
|
||||
]
|
||||
table = SingleTable(table_data)
|
||||
|
||||
# Calculate newlines.
|
||||
max_width = table.column_max_width(1)
|
||||
wrapped_string = '\n'.join(wrap(LONG_STRING, max_width))
|
||||
table.table_data[0][1] = wrapped_string
|
||||
|
||||
print(table.table)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
111
setup.py
Executable file
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python
|
||||
"""Setup script for the project."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
|
||||
from setuptools import Command, setup
|
||||
|
||||
INSTALL_REQUIRES = []
|
||||
LICENSE = 'MIT'
|
||||
NAME = IMPORT = 'terminaltables'
|
||||
VERSION = '3.1.0'
|
||||
|
||||
|
||||
def readme(path='README.rst'):
|
||||
"""Try to read README.rst or return empty string if failed.
|
||||
|
||||
:param str path: Path to README file.
|
||||
|
||||
:return: File contents.
|
||||
:rtype: str
|
||||
"""
|
||||
path = os.path.realpath(os.path.join(os.path.dirname(__file__), path))
|
||||
handle = None
|
||||
url_prefix = 'https://raw.githubusercontent.com/Robpol86/{name}/v{version}/'.format(name=NAME, version=VERSION)
|
||||
try:
|
||||
handle = codecs.open(path, encoding='utf-8')
|
||||
return handle.read(131072).replace('.. image:: docs', '.. image:: {0}docs'.format(url_prefix))
|
||||
except IOError:
|
||||
return ''
|
||||
finally:
|
||||
getattr(handle, 'close', lambda: None)()
|
||||
|
||||
|
||||
class CheckVersion(Command):
|
||||
"""Make sure version strings and other metadata match here, in module/package, tox, and other places."""
|
||||
|
||||
description = 'verify consistent version/etc strings in project'
|
||||
user_options = []
|
||||
|
||||
@classmethod
|
||||
def initialize_options(cls):
|
||||
"""Required by distutils."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def finalize_options(cls):
|
||||
"""Required by distutils."""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def run(cls):
|
||||
"""Check variables."""
|
||||
project = __import__(IMPORT, fromlist=[''])
|
||||
for expected, var in [('@Robpol86', '__author__'), (LICENSE, '__license__'), (VERSION, '__version__')]:
|
||||
if getattr(project, var) != expected:
|
||||
raise SystemExit('Mismatch: {0}'.format(var))
|
||||
# Check changelog.
|
||||
if not re.compile(r'^%s - \d{4}-\d{2}-\d{2}[\r\n]' % VERSION, re.MULTILINE).search(readme()):
|
||||
raise SystemExit('Version not found in readme/changelog file.')
|
||||
# Check tox.
|
||||
if INSTALL_REQUIRES:
|
||||
contents = readme('tox.ini')
|
||||
section = re.compile(r'[\r\n]+install_requires =[\r\n]+(.+?)[\r\n]+\w', re.DOTALL).findall(contents)
|
||||
if not section:
|
||||
raise SystemExit('Missing install_requires section in tox.ini.')
|
||||
in_tox = re.findall(r' ([^=]+)==[\w\d.-]+', section[0])
|
||||
if INSTALL_REQUIRES != in_tox:
|
||||
raise SystemExit('Missing/unordered pinned dependencies in tox.ini.')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
setup(
|
||||
author='@Robpol86',
|
||||
author_email='robpol86@gmail.com',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Environment :: MacOS X',
|
||||
'Environment :: Win32 (MS Windows)',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Terminals',
|
||||
'Topic :: Text Processing :: Markup',
|
||||
],
|
||||
cmdclass=dict(check_version=CheckVersion),
|
||||
description='Generate simple tables in terminals from a nested list of strings.',
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
keywords='Shell Bash ANSI ASCII terminal tables',
|
||||
license=LICENSE,
|
||||
long_description=readme(),
|
||||
name=NAME,
|
||||
packages=[IMPORT],
|
||||
url='https://github.com/Robpol86/' + NAME,
|
||||
version=VERSION,
|
||||
zip_safe=True,
|
||||
)
|
17
terminaltables/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
"""Generate simple tables in terminals from a nested list of strings.
|
||||
|
||||
Use SingleTable or DoubleTable instead of AsciiTable for box-drawing characters.
|
||||
|
||||
https://github.com/Robpol86/terminaltables
|
||||
https://pypi.python.org/pypi/terminaltables
|
||||
"""
|
||||
|
||||
from terminaltables.ascii_table import AsciiTable # noqa
|
||||
from terminaltables.github_table import GithubFlavoredMarkdownTable # noqa
|
||||
from terminaltables.other_tables import DoubleTable # noqa
|
||||
from terminaltables.other_tables import SingleTable # noqa
|
||||
from terminaltables.other_tables import PorcelainTable # noqa
|
||||
|
||||
__author__ = '@Robpol86'
|
||||
__license__ = 'MIT'
|
||||
__version__ = '3.1.0'
|
55
terminaltables/ascii_table.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""AsciiTable is the main table class. To be inherited by other tables. Define convenience methods here."""
|
||||
|
||||
from terminaltables.base_table import BaseTable
|
||||
from terminaltables.terminal_io import terminal_size
|
||||
from terminaltables.width_and_alignment import column_max_width, max_dimensions, table_width
|
||||
|
||||
|
||||
class AsciiTable(BaseTable):
|
||||
"""Draw a table using regular ASCII characters, such as ``+``, ``|``, and ``-``.
|
||||
|
||||
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
:ivar str title: Optional title to show within the top border of the table.
|
||||
:ivar bool inner_column_border: Separates columns.
|
||||
:ivar bool inner_footing_row_border: Show a border before the last row.
|
||||
:ivar bool inner_heading_row_border: Show a border after the first row.
|
||||
:ivar bool inner_row_border: Show a border in between every row.
|
||||
:ivar bool outer_border: Show the top, left, right, and bottom border.
|
||||
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
|
||||
:ivar int padding_left: Number of spaces to pad on the left side of every cell.
|
||||
:ivar int padding_right: Number of spaces to pad on the right side of every cell.
|
||||
"""
|
||||
|
||||
def column_max_width(self, column_number):
|
||||
"""Return the maximum width of a column based on the current terminal width.
|
||||
|
||||
:param int column_number: The column number to query.
|
||||
|
||||
:return: The max width of the column.
|
||||
:rtype: int
|
||||
"""
|
||||
inner_widths = max_dimensions(self.table_data)[0]
|
||||
outer_border = 2 if self.outer_border else 0
|
||||
inner_border = 1 if self.inner_column_border else 0
|
||||
padding = self.padding_left + self.padding_right
|
||||
return column_max_width(inner_widths, column_number, outer_border, inner_border, padding)
|
||||
|
||||
@property
|
||||
def column_widths(self):
|
||||
"""Return a list of integers representing the widths of each table column without padding."""
|
||||
if not self.table_data:
|
||||
return list()
|
||||
return max_dimensions(self.table_data)[0]
|
||||
|
||||
@property
|
||||
def ok(self): # Too late to change API. # pylint: disable=invalid-name
|
||||
"""Return True if the table fits within the terminal width, False if the table breaks."""
|
||||
return self.table_width <= terminal_size()[0]
|
||||
|
||||
@property
|
||||
def table_width(self):
|
||||
"""Return the width of the table including padding and borders."""
|
||||
outer_widths = max_dimensions(self.table_data, self.padding_left, self.padding_right)[2]
|
||||
outer_border = 2 if self.outer_border else 0
|
||||
inner_border = 1 if self.inner_column_border else 0
|
||||
return table_width(outer_widths, outer_border, inner_border)
|
217
terminaltables/base_table.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
"""Base table class. Define just the bare minimum to build tables."""
|
||||
|
||||
from terminaltables.build import build_border, build_row, flatten
|
||||
from terminaltables.width_and_alignment import align_and_pad_cell, max_dimensions
|
||||
|
||||
|
||||
class BaseTable(object):
|
||||
"""Base table class.
|
||||
|
||||
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
:ivar str title: Optional title to show within the top border of the table.
|
||||
:ivar bool inner_column_border: Separates columns.
|
||||
:ivar bool inner_footing_row_border: Show a border before the last row.
|
||||
:ivar bool inner_heading_row_border: Show a border after the first row.
|
||||
:ivar bool inner_row_border: Show a border in between every row.
|
||||
:ivar bool outer_border: Show the top, left, right, and bottom border.
|
||||
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
|
||||
:ivar int padding_left: Number of spaces to pad on the left side of every cell.
|
||||
:ivar int padding_right: Number of spaces to pad on the right side of every cell.
|
||||
"""
|
||||
|
||||
CHAR_F_INNER_HORIZONTAL = '-'
|
||||
CHAR_F_INNER_INTERSECT = '+'
|
||||
CHAR_F_INNER_VERTICAL = '|'
|
||||
CHAR_F_OUTER_LEFT_INTERSECT = '+'
|
||||
CHAR_F_OUTER_LEFT_VERTICAL = '|'
|
||||
CHAR_F_OUTER_RIGHT_INTERSECT = '+'
|
||||
CHAR_F_OUTER_RIGHT_VERTICAL = '|'
|
||||
CHAR_H_INNER_HORIZONTAL = '-'
|
||||
CHAR_H_INNER_INTERSECT = '+'
|
||||
CHAR_H_INNER_VERTICAL = '|'
|
||||
CHAR_H_OUTER_LEFT_INTERSECT = '+'
|
||||
CHAR_H_OUTER_LEFT_VERTICAL = '|'
|
||||
CHAR_H_OUTER_RIGHT_INTERSECT = '+'
|
||||
CHAR_H_OUTER_RIGHT_VERTICAL = '|'
|
||||
CHAR_INNER_HORIZONTAL = '-'
|
||||
CHAR_INNER_INTERSECT = '+'
|
||||
CHAR_INNER_VERTICAL = '|'
|
||||
CHAR_OUTER_BOTTOM_HORIZONTAL = '-'
|
||||
CHAR_OUTER_BOTTOM_INTERSECT = '+'
|
||||
CHAR_OUTER_BOTTOM_LEFT = '+'
|
||||
CHAR_OUTER_BOTTOM_RIGHT = '+'
|
||||
CHAR_OUTER_LEFT_INTERSECT = '+'
|
||||
CHAR_OUTER_LEFT_VERTICAL = '|'
|
||||
CHAR_OUTER_RIGHT_INTERSECT = '+'
|
||||
CHAR_OUTER_RIGHT_VERTICAL = '|'
|
||||
CHAR_OUTER_TOP_HORIZONTAL = '-'
|
||||
CHAR_OUTER_TOP_INTERSECT = '+'
|
||||
CHAR_OUTER_TOP_LEFT = '+'
|
||||
CHAR_OUTER_TOP_RIGHT = '+'
|
||||
|
||||
def __init__(self, table_data, title=None):
|
||||
"""Constructor.
|
||||
|
||||
:param iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
:param title: Optional title to show within the top border of the table.
|
||||
"""
|
||||
self.table_data = table_data
|
||||
self.title = title
|
||||
|
||||
self.inner_column_border = True
|
||||
self.inner_footing_row_border = False
|
||||
self.inner_heading_row_border = True
|
||||
self.inner_row_border = False
|
||||
self.outer_border = True
|
||||
|
||||
self.justify_columns = dict() # {0: 'right', 1: 'left', 2: 'center'}
|
||||
self.padding_left = 1
|
||||
self.padding_right = 1
|
||||
|
||||
def horizontal_border(self, style, outer_widths):
|
||||
"""Build any kind of horizontal border for the table.
|
||||
|
||||
:param str style: Type of border to return.
|
||||
:param iter outer_widths: List of widths (with padding) for each column.
|
||||
|
||||
:return: Prepared border as a tuple of strings.
|
||||
:rtype: tuple
|
||||
"""
|
||||
if style == 'top':
|
||||
horizontal = self.CHAR_OUTER_TOP_HORIZONTAL
|
||||
left = self.CHAR_OUTER_TOP_LEFT
|
||||
intersect = self.CHAR_OUTER_TOP_INTERSECT if self.inner_column_border else ''
|
||||
right = self.CHAR_OUTER_TOP_RIGHT
|
||||
title = self.title
|
||||
elif style == 'bottom':
|
||||
horizontal = self.CHAR_OUTER_BOTTOM_HORIZONTAL
|
||||
left = self.CHAR_OUTER_BOTTOM_LEFT
|
||||
intersect = self.CHAR_OUTER_BOTTOM_INTERSECT if self.inner_column_border else ''
|
||||
right = self.CHAR_OUTER_BOTTOM_RIGHT
|
||||
title = None
|
||||
elif style == 'heading':
|
||||
horizontal = self.CHAR_H_INNER_HORIZONTAL
|
||||
left = self.CHAR_H_OUTER_LEFT_INTERSECT if self.outer_border else ''
|
||||
intersect = self.CHAR_H_INNER_INTERSECT if self.inner_column_border else ''
|
||||
right = self.CHAR_H_OUTER_RIGHT_INTERSECT if self.outer_border else ''
|
||||
title = None
|
||||
elif style == 'footing':
|
||||
horizontal = self.CHAR_F_INNER_HORIZONTAL
|
||||
left = self.CHAR_F_OUTER_LEFT_INTERSECT if self.outer_border else ''
|
||||
intersect = self.CHAR_F_INNER_INTERSECT if self.inner_column_border else ''
|
||||
right = self.CHAR_F_OUTER_RIGHT_INTERSECT if self.outer_border else ''
|
||||
title = None
|
||||
else:
|
||||
horizontal = self.CHAR_INNER_HORIZONTAL
|
||||
left = self.CHAR_OUTER_LEFT_INTERSECT if self.outer_border else ''
|
||||
intersect = self.CHAR_INNER_INTERSECT if self.inner_column_border else ''
|
||||
right = self.CHAR_OUTER_RIGHT_INTERSECT if self.outer_border else ''
|
||||
title = None
|
||||
return build_border(outer_widths, horizontal, left, intersect, right, title)
|
||||
|
||||
def gen_row_lines(self, row, style, inner_widths, height):
|
||||
r"""Combine cells in row and group them into lines with vertical borders.
|
||||
|
||||
Caller is expected to pass yielded lines to ''.join() to combine them into a printable line. Caller must append
|
||||
newline character to the end of joined line.
|
||||
|
||||
In:
|
||||
['Row One Column One', 'Two', 'Three']
|
||||
Out:
|
||||
[
|
||||
('|', ' Row One Column One ', '|', ' Two ', '|', ' Three ', '|'),
|
||||
]
|
||||
|
||||
In:
|
||||
['Row One\nColumn One', 'Two', 'Three'],
|
||||
Out:
|
||||
[
|
||||
('|', ' Row One ', '|', ' Two ', '|', ' Three ', '|'),
|
||||
('|', ' Column One ', '|', ' ', '|', ' ', '|'),
|
||||
]
|
||||
|
||||
:param iter row: One row in the table. List of cells.
|
||||
:param str style: Type of border characters to use.
|
||||
:param iter inner_widths: List of widths (no padding) for each column.
|
||||
:param int height: Inner height (no padding) (number of lines) to expand row to.
|
||||
|
||||
:return: Yields lines split into components in a list. Caller must ''.join() line.
|
||||
"""
|
||||
cells_in_row = list()
|
||||
|
||||
# Resize row if it doesn't have enough cells.
|
||||
if len(row) != len(inner_widths):
|
||||
row = row + [''] * (len(inner_widths) - len(row))
|
||||
|
||||
# Pad and align each cell. Split each cell into lines to support multi-line cells.
|
||||
for i, cell in enumerate(row):
|
||||
align = (self.justify_columns.get(i),)
|
||||
inner_dimensions = (inner_widths[i], height)
|
||||
padding = (self.padding_left, self.padding_right, 0, 0)
|
||||
cells_in_row.append(align_and_pad_cell(cell, align, inner_dimensions, padding))
|
||||
|
||||
# Determine border characters.
|
||||
if style == 'heading':
|
||||
left = self.CHAR_H_OUTER_LEFT_VERTICAL if self.outer_border else ''
|
||||
center = self.CHAR_H_INNER_VERTICAL if self.inner_column_border else ''
|
||||
right = self.CHAR_H_OUTER_RIGHT_VERTICAL if self.outer_border else ''
|
||||
elif style == 'footing':
|
||||
left = self.CHAR_F_OUTER_LEFT_VERTICAL if self.outer_border else ''
|
||||
center = self.CHAR_F_INNER_VERTICAL if self.inner_column_border else ''
|
||||
right = self.CHAR_F_OUTER_RIGHT_VERTICAL if self.outer_border else ''
|
||||
else:
|
||||
left = self.CHAR_OUTER_LEFT_VERTICAL if self.outer_border else ''
|
||||
center = self.CHAR_INNER_VERTICAL if self.inner_column_border else ''
|
||||
right = self.CHAR_OUTER_RIGHT_VERTICAL if self.outer_border else ''
|
||||
|
||||
# Yield each line.
|
||||
for line in build_row(cells_in_row, left, center, right):
|
||||
yield line
|
||||
|
||||
def gen_table(self, inner_widths, inner_heights, outer_widths):
|
||||
"""Combine everything and yield every line of the entire table with borders.
|
||||
|
||||
:param iter inner_widths: List of widths (no padding) for each column.
|
||||
:param iter inner_heights: List of heights (no padding) for each row.
|
||||
:param iter outer_widths: List of widths (with padding) for each column.
|
||||
:return:
|
||||
"""
|
||||
# Yield top border.
|
||||
if self.outer_border:
|
||||
yield self.horizontal_border('top', outer_widths)
|
||||
|
||||
# Yield table body.
|
||||
row_count = len(self.table_data)
|
||||
last_row_index, before_last_row_index = row_count - 1, row_count - 2
|
||||
for i, row in enumerate(self.table_data):
|
||||
# Yield the row line by line (e.g. multi-line rows).
|
||||
if self.inner_heading_row_border and i == 0:
|
||||
style = 'heading'
|
||||
elif self.inner_footing_row_border and i == last_row_index:
|
||||
style = 'footing'
|
||||
else:
|
||||
style = 'row'
|
||||
for line in self.gen_row_lines(row, style, inner_widths, inner_heights[i]):
|
||||
yield line
|
||||
# If this is the last row then break. No separator needed.
|
||||
if i == last_row_index:
|
||||
break
|
||||
# Yield heading separator.
|
||||
if self.inner_heading_row_border and i == 0:
|
||||
yield self.horizontal_border('heading', outer_widths)
|
||||
# Yield footing separator.
|
||||
elif self.inner_footing_row_border and i == before_last_row_index:
|
||||
yield self.horizontal_border('footing', outer_widths)
|
||||
# Yield row separator.
|
||||
elif self.inner_row_border:
|
||||
yield self.horizontal_border('row', outer_widths)
|
||||
|
||||
# Yield bottom border.
|
||||
if self.outer_border:
|
||||
yield self.horizontal_border('bottom', outer_widths)
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
"""Return a large string of the entire table ready to be printed to the terminal."""
|
||||
dimensions = max_dimensions(self.table_data, self.padding_left, self.padding_right)[:3]
|
||||
return flatten(self.gen_table(*dimensions))
|
151
terminaltables/build.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
"""Combine cells into rows."""
|
||||
|
||||
from terminaltables.width_and_alignment import visible_width
|
||||
|
||||
|
||||
def combine(line, left, intersect, right):
|
||||
"""Zip borders between items in `line`.
|
||||
|
||||
e.g. ('l', '1', 'c', '2', 'c', '3', 'r')
|
||||
|
||||
:param iter line: List to iterate.
|
||||
:param left: Left border.
|
||||
:param intersect: Column separator.
|
||||
:param right: Right border.
|
||||
|
||||
:return: Yields combined objects.
|
||||
"""
|
||||
# Yield left border.
|
||||
if left:
|
||||
yield left
|
||||
|
||||
# Yield items with intersect characters.
|
||||
if intersect:
|
||||
try:
|
||||
for j, i in enumerate(line, start=-len(line) + 1):
|
||||
yield i
|
||||
if j:
|
||||
yield intersect
|
||||
except TypeError: # Generator.
|
||||
try:
|
||||
item = next(line)
|
||||
except StopIteration: # Was empty all along.
|
||||
pass
|
||||
else:
|
||||
while True:
|
||||
yield item
|
||||
try:
|
||||
peek = next(line)
|
||||
except StopIteration:
|
||||
break
|
||||
yield intersect
|
||||
item = peek
|
||||
else:
|
||||
for i in line:
|
||||
yield i
|
||||
|
||||
# Yield right border.
|
||||
if right:
|
||||
yield right
|
||||
|
||||
|
||||
def build_border(outer_widths, horizontal, left, intersect, right, title=None):
|
||||
"""Build the top/bottom/middle row. Optionally embed the table title within the border.
|
||||
|
||||
Title is hidden if it doesn't fit between the left/right characters/edges.
|
||||
|
||||
Example return value:
|
||||
('<', '-----', '+', '------', '+', '-------', '>')
|
||||
('<', 'My Table', '----', '+', '------->')
|
||||
|
||||
:param iter outer_widths: List of widths (with padding) for each column.
|
||||
:param str horizontal: Character to stretch across each column.
|
||||
:param str left: Left border.
|
||||
:param str intersect: Column separator.
|
||||
:param str right: Right border.
|
||||
:param title: Overlay the title on the border between the left and right characters.
|
||||
|
||||
:return: Returns a generator of strings representing a border.
|
||||
:rtype: iter
|
||||
"""
|
||||
length = 0
|
||||
|
||||
# Hide title if it doesn't fit.
|
||||
if title is not None and outer_widths:
|
||||
try:
|
||||
length = visible_width(title)
|
||||
except TypeError:
|
||||
title = str(title)
|
||||
length = visible_width(title)
|
||||
if length > sum(outer_widths) + len(intersect) * (len(outer_widths) - 1):
|
||||
title = None
|
||||
|
||||
# Handle no title.
|
||||
if title is None or not outer_widths or not horizontal:
|
||||
return combine((horizontal * c for c in outer_widths), left, intersect, right)
|
||||
|
||||
# Handle title fitting in the first column.
|
||||
if length == outer_widths[0]:
|
||||
return combine([title] + [horizontal * c for c in outer_widths[1:]], left, intersect, right)
|
||||
if length < outer_widths[0]:
|
||||
columns = [title + horizontal * (outer_widths[0] - length)] + [horizontal * c for c in outer_widths[1:]]
|
||||
return combine(columns, left, intersect, right)
|
||||
|
||||
# Handle wide titles/narrow columns.
|
||||
columns_and_intersects = [title]
|
||||
for width in combine(outer_widths, None, bool(intersect), None):
|
||||
# If title is taken care of.
|
||||
if length < 1:
|
||||
columns_and_intersects.append(intersect if width is True else horizontal * width)
|
||||
# If title's last character overrides an intersect character.
|
||||
elif width is True and length == 1:
|
||||
length = 0
|
||||
# If this is an intersect character that is overridden by the title.
|
||||
elif width is True:
|
||||
length -= 1
|
||||
# If title's last character is within a column.
|
||||
elif width >= length:
|
||||
columns_and_intersects[0] += horizontal * (width - length) # Append horizontal chars to title.
|
||||
length = 0
|
||||
# If remainder of title won't fit in a column.
|
||||
else:
|
||||
length -= width
|
||||
|
||||
return combine(columns_and_intersects, left, None, right)
|
||||
|
||||
|
||||
def build_row(row, left, center, right):
|
||||
"""Combine single or multi-lined cells into a single row of list of lists including borders.
|
||||
|
||||
Row must already be padded and extended so each cell has the same number of lines.
|
||||
|
||||
Example return value:
|
||||
[
|
||||
['>', 'Left ', '|', 'Center', '|', 'Right', '<'],
|
||||
['>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'],
|
||||
]
|
||||
|
||||
:param iter row: List of cells for one row.
|
||||
:param str left: Left border.
|
||||
:param str center: Column separator.
|
||||
:param str right: Right border.
|
||||
|
||||
:return: Yields other generators that yield strings.
|
||||
:rtype: iter
|
||||
"""
|
||||
if not row or not row[0]:
|
||||
yield combine((), left, center, right)
|
||||
return
|
||||
for row_index in range(len(row[0])):
|
||||
yield combine((c[row_index] for c in row), left, center, right)
|
||||
|
||||
|
||||
def flatten(table):
|
||||
"""Flatten table data into a single string with newlines.
|
||||
|
||||
:param iter table: Padded and bordered table data.
|
||||
|
||||
:return: Joined rows/cells.
|
||||
:rtype: str
|
||||
"""
|
||||
return '\n'.join(''.join(r) for r in table)
|
70
terminaltables/github_table.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
"""GithubFlavoredMarkdownTable class."""
|
||||
|
||||
from terminaltables.ascii_table import AsciiTable
|
||||
from terminaltables.build import combine
|
||||
|
||||
|
||||
class GithubFlavoredMarkdownTable(AsciiTable):
|
||||
"""Github flavored markdown table.
|
||||
|
||||
https://help.github.com/articles/github-flavored-markdown/#tables
|
||||
|
||||
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
|
||||
"""
|
||||
|
||||
def __init__(self, table_data):
|
||||
"""Constructor.
|
||||
|
||||
:param iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
"""
|
||||
# Github flavored markdown table won't support title.
|
||||
super(GithubFlavoredMarkdownTable, self).__init__(table_data)
|
||||
|
||||
def horizontal_border(self, _, outer_widths):
|
||||
"""Handle the GitHub heading border.
|
||||
|
||||
E.g.:
|
||||
|:---|:---:|---:|----|
|
||||
|
||||
:param _: Unused.
|
||||
:param iter outer_widths: List of widths (with padding) for each column.
|
||||
|
||||
:return: Prepared border strings in a generator.
|
||||
:rtype: iter
|
||||
"""
|
||||
horizontal = str(self.CHAR_INNER_HORIZONTAL)
|
||||
left = self.CHAR_OUTER_LEFT_VERTICAL
|
||||
intersect = self.CHAR_INNER_VERTICAL
|
||||
right = self.CHAR_OUTER_RIGHT_VERTICAL
|
||||
|
||||
columns = list()
|
||||
for i, width in enumerate(outer_widths):
|
||||
justify = self.justify_columns.get(i)
|
||||
width = max(3, width) # Width should be at least 3 so justification can be applied.
|
||||
if justify == 'left':
|
||||
columns.append(':' + horizontal * (width - 1))
|
||||
elif justify == 'right':
|
||||
columns.append(horizontal * (width - 1) + ':')
|
||||
elif justify == 'center':
|
||||
columns.append(':' + horizontal * (width - 2) + ':')
|
||||
else:
|
||||
columns.append(horizontal * width)
|
||||
|
||||
return combine(columns, left, intersect, right)
|
||||
|
||||
def gen_table(self, inner_widths, inner_heights, outer_widths):
|
||||
"""Combine everything and yield every line of the entire table with borders.
|
||||
|
||||
:param iter inner_widths: List of widths (no padding) for each column.
|
||||
:param iter inner_heights: List of heights (no padding) for each row.
|
||||
:param iter outer_widths: List of widths (with padding) for each column.
|
||||
:return:
|
||||
"""
|
||||
for i, row in enumerate(self.table_data):
|
||||
# Yield the row line by line (e.g. multi-line rows).
|
||||
for line in self.gen_row_lines(row, 'row', inner_widths, inner_heights[i]):
|
||||
yield line
|
||||
# Yield heading separator.
|
||||
if i == 0:
|
||||
yield self.horizontal_border(None, outer_widths)
|
177
terminaltables/other_tables.py
Normal file
|
@ -0,0 +1,177 @@
|
|||
"""Additional simple tables defined here."""
|
||||
|
||||
from terminaltables.ascii_table import AsciiTable
|
||||
from terminaltables.terminal_io import IS_WINDOWS
|
||||
|
||||
|
||||
class UnixTable(AsciiTable):
|
||||
"""Draw a table using box-drawing characters on Unix platforms. Table borders won't have any gaps between lines.
|
||||
|
||||
Similar to the tables shown on PC BIOS boot messages, but not double-lined.
|
||||
"""
|
||||
|
||||
CHAR_F_INNER_HORIZONTAL = '\033(0\x71\033(B'
|
||||
CHAR_F_INNER_INTERSECT = '\033(0\x6e\033(B'
|
||||
CHAR_F_INNER_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_F_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B'
|
||||
CHAR_F_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_F_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B'
|
||||
CHAR_F_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_H_INNER_HORIZONTAL = '\033(0\x71\033(B'
|
||||
CHAR_H_INNER_INTERSECT = '\033(0\x6e\033(B'
|
||||
CHAR_H_INNER_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_H_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B'
|
||||
CHAR_H_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_H_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B'
|
||||
CHAR_H_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_INNER_HORIZONTAL = '\033(0\x71\033(B'
|
||||
CHAR_INNER_INTERSECT = '\033(0\x6e\033(B'
|
||||
CHAR_INNER_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_OUTER_BOTTOM_HORIZONTAL = '\033(0\x71\033(B'
|
||||
CHAR_OUTER_BOTTOM_INTERSECT = '\033(0\x76\033(B'
|
||||
CHAR_OUTER_BOTTOM_LEFT = '\033(0\x6d\033(B'
|
||||
CHAR_OUTER_BOTTOM_RIGHT = '\033(0\x6a\033(B'
|
||||
CHAR_OUTER_LEFT_INTERSECT = '\033(0\x74\033(B'
|
||||
CHAR_OUTER_LEFT_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_OUTER_RIGHT_INTERSECT = '\033(0\x75\033(B'
|
||||
CHAR_OUTER_RIGHT_VERTICAL = '\033(0\x78\033(B'
|
||||
CHAR_OUTER_TOP_HORIZONTAL = '\033(0\x71\033(B'
|
||||
CHAR_OUTER_TOP_INTERSECT = '\033(0\x77\033(B'
|
||||
CHAR_OUTER_TOP_LEFT = '\033(0\x6c\033(B'
|
||||
CHAR_OUTER_TOP_RIGHT = '\033(0\x6b\033(B'
|
||||
|
||||
@property
|
||||
def table(self):
|
||||
"""Return a large string of the entire table ready to be printed to the terminal."""
|
||||
ascii_table = super(UnixTable, self).table
|
||||
optimized = ascii_table.replace('\033(B\033(0', '')
|
||||
return optimized
|
||||
|
||||
|
||||
class WindowsTable(AsciiTable):
|
||||
"""Draw a table using box-drawing characters on Windows platforms. This uses Code Page 437. Single-line borders.
|
||||
|
||||
From: http://en.wikipedia.org/wiki/Code_page_437#Characters
|
||||
"""
|
||||
|
||||
CHAR_F_INNER_HORIZONTAL = b'\xc4'.decode('ibm437')
|
||||
CHAR_F_INNER_INTERSECT = b'\xc5'.decode('ibm437')
|
||||
CHAR_F_INNER_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_F_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437')
|
||||
CHAR_F_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_F_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437')
|
||||
CHAR_F_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_H_INNER_HORIZONTAL = b'\xc4'.decode('ibm437')
|
||||
CHAR_H_INNER_INTERSECT = b'\xc5'.decode('ibm437')
|
||||
CHAR_H_INNER_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_H_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437')
|
||||
CHAR_H_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_H_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437')
|
||||
CHAR_H_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_INNER_HORIZONTAL = b'\xc4'.decode('ibm437')
|
||||
CHAR_INNER_INTERSECT = b'\xc5'.decode('ibm437')
|
||||
CHAR_INNER_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_HORIZONTAL = b'\xc4'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_INTERSECT = b'\xc1'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_LEFT = b'\xc0'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_RIGHT = b'\xd9'.decode('ibm437')
|
||||
CHAR_OUTER_LEFT_INTERSECT = b'\xc3'.decode('ibm437')
|
||||
CHAR_OUTER_LEFT_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_OUTER_RIGHT_INTERSECT = b'\xb4'.decode('ibm437')
|
||||
CHAR_OUTER_RIGHT_VERTICAL = b'\xb3'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_HORIZONTAL = b'\xc4'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_INTERSECT = b'\xc2'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_LEFT = b'\xda'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_RIGHT = b'\xbf'.decode('ibm437')
|
||||
|
||||
|
||||
class WindowsTableDouble(AsciiTable):
|
||||
"""Draw a table using box-drawing characters on Windows platforms. This uses Code Page 437. Double-line borders."""
|
||||
|
||||
CHAR_F_INNER_HORIZONTAL = b'\xcd'.decode('ibm437')
|
||||
CHAR_F_INNER_INTERSECT = b'\xce'.decode('ibm437')
|
||||
CHAR_F_INNER_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_F_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437')
|
||||
CHAR_F_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_F_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437')
|
||||
CHAR_F_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_H_INNER_HORIZONTAL = b'\xcd'.decode('ibm437')
|
||||
CHAR_H_INNER_INTERSECT = b'\xce'.decode('ibm437')
|
||||
CHAR_H_INNER_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_H_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437')
|
||||
CHAR_H_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_H_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437')
|
||||
CHAR_H_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_INNER_HORIZONTAL = b'\xcd'.decode('ibm437')
|
||||
CHAR_INNER_INTERSECT = b'\xce'.decode('ibm437')
|
||||
CHAR_INNER_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_HORIZONTAL = b'\xcd'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_INTERSECT = b'\xca'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_LEFT = b'\xc8'.decode('ibm437')
|
||||
CHAR_OUTER_BOTTOM_RIGHT = b'\xbc'.decode('ibm437')
|
||||
CHAR_OUTER_LEFT_INTERSECT = b'\xcc'.decode('ibm437')
|
||||
CHAR_OUTER_LEFT_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_OUTER_RIGHT_INTERSECT = b'\xb9'.decode('ibm437')
|
||||
CHAR_OUTER_RIGHT_VERTICAL = b'\xba'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_HORIZONTAL = b'\xcd'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_INTERSECT = b'\xcb'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_LEFT = b'\xc9'.decode('ibm437')
|
||||
CHAR_OUTER_TOP_RIGHT = b'\xbb'.decode('ibm437')
|
||||
|
||||
|
||||
class SingleTable(WindowsTable if IS_WINDOWS else UnixTable):
|
||||
"""Cross-platform table with single-line box-drawing characters.
|
||||
|
||||
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
:ivar str title: Optional title to show within the top border of the table.
|
||||
:ivar bool inner_column_border: Separates columns.
|
||||
:ivar bool inner_footing_row_border: Show a border before the last row.
|
||||
:ivar bool inner_heading_row_border: Show a border after the first row.
|
||||
:ivar bool inner_row_border: Show a border in between every row.
|
||||
:ivar bool outer_border: Show the top, left, right, and bottom border.
|
||||
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
|
||||
:ivar int padding_left: Number of spaces to pad on the left side of every cell.
|
||||
:ivar int padding_right: Number of spaces to pad on the right side of every cell.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DoubleTable(WindowsTableDouble):
|
||||
"""Cross-platform table with box-drawing characters. On Windows it's double borders, on Linux/OSX it's unicode.
|
||||
|
||||
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
:ivar str title: Optional title to show within the top border of the table.
|
||||
:ivar bool inner_column_border: Separates columns.
|
||||
:ivar bool inner_footing_row_border: Show a border before the last row.
|
||||
:ivar bool inner_heading_row_border: Show a border after the first row.
|
||||
:ivar bool inner_row_border: Show a border in between every row.
|
||||
:ivar bool outer_border: Show the top, left, right, and bottom border.
|
||||
:ivar dict justify_columns: Horizontal justification. Keys are column indexes (int). Values are right/left/center.
|
||||
:ivar int padding_left: Number of spaces to pad on the left side of every cell.
|
||||
:ivar int padding_right: Number of spaces to pad on the right side of every cell.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PorcelainTable(AsciiTable):
|
||||
"""An AsciiTable stripped to a minimum.
|
||||
|
||||
Meant to be machine passable and roughly follow format set by git --porcelain option (hence the name).
|
||||
|
||||
:ivar iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
"""
|
||||
|
||||
def __init__(self, table_data):
|
||||
"""Constructor.
|
||||
|
||||
:param iter table_data: List (empty or list of lists of strings) representing the table.
|
||||
"""
|
||||
# Porcelain table won't support title since it has no outer birders.
|
||||
super(PorcelainTable, self).__init__(table_data)
|
||||
|
||||
# Removes outer border, and inner footing and header row borders.
|
||||
self.inner_footing_row_border = False
|
||||
self.inner_heading_row_border = False
|
||||
self.outer_border = False
|
98
terminaltables/terminal_io.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
"""Get info about the current terminal window/screen buffer."""
|
||||
|
||||
import ctypes
|
||||
import struct
|
||||
import sys
|
||||
|
||||
DEFAULT_HEIGHT = 24
|
||||
DEFAULT_WIDTH = 79
|
||||
INVALID_HANDLE_VALUE = -1
|
||||
IS_WINDOWS = sys.platform == 'win32'
|
||||
STD_ERROR_HANDLE = -12
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
|
||||
|
||||
def get_console_info(kernel32, handle):
|
||||
"""Get information about this current console window (Windows only).
|
||||
|
||||
https://github.com/Robpol86/colorclass/blob/ab42da59/colorclass/windows.py#L111
|
||||
|
||||
:raise OSError: When handle is invalid or GetConsoleScreenBufferInfo API call fails.
|
||||
|
||||
:param ctypes.windll.kernel32 kernel32: Loaded kernel32 instance.
|
||||
:param int handle: stderr or stdout handle.
|
||||
|
||||
:return: Width (number of characters) and height (number of lines) of the terminal.
|
||||
:rtype: tuple
|
||||
"""
|
||||
if handle == INVALID_HANDLE_VALUE:
|
||||
raise OSError('Invalid handle.')
|
||||
|
||||
# Query Win32 API.
|
||||
lpcsbi = ctypes.create_string_buffer(22) # Populated by GetConsoleScreenBufferInfo.
|
||||
if not kernel32.GetConsoleScreenBufferInfo(handle, lpcsbi):
|
||||
raise ctypes.WinError() # Subclass of OSError.
|
||||
|
||||
# Parse data.
|
||||
left, top, right, bottom = struct.unpack('hhhhHhhhhhh', lpcsbi.raw)[5:-2]
|
||||
width, height = right - left, bottom - top
|
||||
return width, height
|
||||
|
||||
|
||||
def terminal_size(kernel32=None):
|
||||
"""Get the width and height of the terminal.
|
||||
|
||||
http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/
|
||||
http://stackoverflow.com/questions/17993814/why-the-irrelevant-code-made-a-difference
|
||||
|
||||
:param kernel32: Optional mock kernel32 object. For testing.
|
||||
|
||||
:return: Width (number of characters) and height (number of lines) of the terminal.
|
||||
:rtype: tuple
|
||||
"""
|
||||
if IS_WINDOWS:
|
||||
kernel32 = kernel32 or ctypes.windll.kernel32
|
||||
try:
|
||||
return get_console_info(kernel32, kernel32.GetStdHandle(STD_ERROR_HANDLE))
|
||||
except OSError:
|
||||
try:
|
||||
return get_console_info(kernel32, kernel32.GetStdHandle(STD_OUTPUT_HANDLE))
|
||||
except OSError:
|
||||
return DEFAULT_WIDTH, DEFAULT_HEIGHT
|
||||
|
||||
try:
|
||||
device = __import__('fcntl').ioctl(0, __import__('termios').TIOCGWINSZ, '\0\0\0\0\0\0\0\0')
|
||||
except IOError:
|
||||
return DEFAULT_WIDTH, DEFAULT_HEIGHT
|
||||
height, width = struct.unpack('hhhh', device)[:2]
|
||||
return width, height
|
||||
|
||||
|
||||
def set_terminal_title(title, kernel32=None):
|
||||
"""Set the terminal title.
|
||||
|
||||
:param title: The title to set (string, unicode, bytes accepted).
|
||||
:param kernel32: Optional mock kernel32 object. For testing.
|
||||
|
||||
:return: If title changed successfully (Windows only, always True on Linux/OSX).
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
title_bytes = title.encode('utf-8')
|
||||
except AttributeError:
|
||||
title_bytes = title
|
||||
|
||||
if IS_WINDOWS:
|
||||
kernel32 = kernel32 or ctypes.windll.kernel32
|
||||
try:
|
||||
is_ascii = all(ord(c) < 128 for c in title) # str/unicode.
|
||||
except TypeError:
|
||||
is_ascii = all(c < 128 for c in title) # bytes.
|
||||
if is_ascii:
|
||||
return kernel32.SetConsoleTitleA(title_bytes) != 0
|
||||
else:
|
||||
return kernel32.SetConsoleTitleW(title) != 0
|
||||
|
||||
# Linux/OSX.
|
||||
sys.stdout.write(b'\033]0;' + title_bytes + b'\007')
|
||||
return True
|
160
terminaltables/width_and_alignment.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
"""Functions that handle alignment, padding, widths, etc."""
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
from terminaltables.terminal_io import terminal_size
|
||||
|
||||
RE_COLOR_ANSI = re.compile(r'(\033\[[\d;]+m)')
|
||||
|
||||
|
||||
def visible_width(string):
|
||||
"""Get the visible width of a unicode string.
|
||||
|
||||
Some CJK unicode characters are more than one byte unlike ASCII and latin unicode characters.
|
||||
|
||||
From: https://github.com/Robpol86/terminaltables/pull/9
|
||||
|
||||
:param str string: String to measure.
|
||||
|
||||
:return: String's width.
|
||||
:rtype: int
|
||||
"""
|
||||
if '\033' in string:
|
||||
string = RE_COLOR_ANSI.sub('', string)
|
||||
|
||||
# Convert to unicode.
|
||||
try:
|
||||
string = string.decode('u8')
|
||||
except (AttributeError, UnicodeEncodeError):
|
||||
pass
|
||||
|
||||
width = 0
|
||||
for char in string:
|
||||
if unicodedata.east_asian_width(char) in ('F', 'W'):
|
||||
width += 2
|
||||
else:
|
||||
width += 1
|
||||
|
||||
return width
|
||||
|
||||
|
||||
def align_and_pad_cell(string, align, inner_dimensions, padding, space=' '):
|
||||
"""Align a string horizontally and vertically. Also add additional padding in both dimensions.
|
||||
|
||||
:param str string: Input string to operate on.
|
||||
:param tuple align: Tuple that contains one of left/center/right and/or top/middle/bottom.
|
||||
:param tuple inner_dimensions: Width and height ints to expand string to without padding.
|
||||
:param iter padding: Number of space chars for left, right, top, and bottom (4 ints).
|
||||
:param str space: Character to use as white space for resizing/padding (use single visible chars only).
|
||||
|
||||
:return: Padded cell split into lines.
|
||||
:rtype: list
|
||||
"""
|
||||
if not hasattr(string, 'splitlines'):
|
||||
string = str(string)
|
||||
|
||||
# Handle trailing newlines or empty strings, str.splitlines() does not satisfy.
|
||||
lines = string.splitlines() or ['']
|
||||
if string.endswith('\n'):
|
||||
lines.append('')
|
||||
|
||||
# Vertically align and pad.
|
||||
if 'bottom' in align:
|
||||
lines = ([''] * (inner_dimensions[1] - len(lines) + padding[2])) + lines + ([''] * padding[3])
|
||||
elif 'middle' in align:
|
||||
delta = inner_dimensions[1] - len(lines)
|
||||
lines = ([''] * (delta // 2 + delta % 2 + padding[2])) + lines + ([''] * (delta // 2 + padding[3]))
|
||||
else:
|
||||
lines = ([''] * padding[2]) + lines + ([''] * (inner_dimensions[1] - len(lines) + padding[3]))
|
||||
|
||||
# Horizontally align and pad.
|
||||
for i, line in enumerate(lines):
|
||||
new_width = inner_dimensions[0] + len(line) - visible_width(line)
|
||||
if 'right' in align:
|
||||
lines[i] = line.rjust(padding[0] + new_width, space) + (space * padding[1])
|
||||
elif 'center' in align:
|
||||
lines[i] = (space * padding[0]) + line.center(new_width, space) + (space * padding[1])
|
||||
else:
|
||||
lines[i] = (space * padding[0]) + line.ljust(new_width + padding[1], space)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def max_dimensions(table_data, padding_left=0, padding_right=0, padding_top=0, padding_bottom=0):
|
||||
"""Get maximum widths of each column and maximum height of each row.
|
||||
|
||||
:param iter table_data: List of list of strings (unmodified table data).
|
||||
:param int padding_left: Number of space chars on left side of cell.
|
||||
:param int padding_right: Number of space chars on right side of cell.
|
||||
:param int padding_top: Number of empty lines on top side of cell.
|
||||
:param int padding_bottom: Number of empty lines on bottom side of cell.
|
||||
|
||||
:return: 4-item tuple of n-item lists. Inner column widths and row heights, outer column widths and row heights.
|
||||
:rtype: tuple
|
||||
"""
|
||||
inner_widths = [0] * (max(len(r) for r in table_data) if table_data else 0)
|
||||
inner_heights = [0] * len(table_data)
|
||||
|
||||
# Find max width and heights.
|
||||
for j, row in enumerate(table_data):
|
||||
for i, cell in enumerate(row):
|
||||
if not hasattr(cell, 'count') or not hasattr(cell, 'splitlines'):
|
||||
cell = str(cell)
|
||||
if not cell:
|
||||
continue
|
||||
inner_heights[j] = max(inner_heights[j], cell.count('\n') + 1)
|
||||
inner_widths[i] = max(inner_widths[i], *[visible_width(l) for l in cell.splitlines()])
|
||||
|
||||
# Calculate with padding.
|
||||
outer_widths = [padding_left + i + padding_right for i in inner_widths]
|
||||
outer_heights = [padding_top + i + padding_bottom for i in inner_heights]
|
||||
|
||||
return inner_widths, inner_heights, outer_widths, outer_heights
|
||||
|
||||
|
||||
def column_max_width(inner_widths, column_number, outer_border, inner_border, padding):
|
||||
"""Determine the maximum width of a column based on the current terminal width.
|
||||
|
||||
:param iter inner_widths: List of widths (no padding) for each column.
|
||||
:param int column_number: The column number to query.
|
||||
:param int outer_border: Sum of left and right outer border visible widths.
|
||||
:param int inner_border: Visible width of the inner border character.
|
||||
:param int padding: Total padding per cell (left + right padding).
|
||||
|
||||
:return: The maximum width the column can be without causing line wrapping.
|
||||
"""
|
||||
column_count = len(inner_widths)
|
||||
terminal_width = terminal_size()[0]
|
||||
|
||||
# Count how much space padding, outer, and inner borders take up.
|
||||
non_data_space = outer_border
|
||||
non_data_space += inner_border * (column_count - 1)
|
||||
non_data_space += column_count * padding
|
||||
|
||||
# Exclude selected column's width.
|
||||
data_space = sum(inner_widths) - inner_widths[column_number]
|
||||
|
||||
return terminal_width - data_space - non_data_space
|
||||
|
||||
|
||||
def table_width(outer_widths, outer_border, inner_border):
|
||||
"""Determine the width of the entire table including borders and padding.
|
||||
|
||||
:param iter outer_widths: List of widths (with padding) for each column.
|
||||
:param int outer_border: Sum of left and right outer border visible widths.
|
||||
:param int inner_border: Visible width of the inner border character.
|
||||
|
||||
:return: The width of the table.
|
||||
:rtype: int
|
||||
"""
|
||||
column_count = len(outer_widths)
|
||||
|
||||
# Count how much space outer and inner borders take up.
|
||||
non_data_space = outer_border
|
||||
if column_count:
|
||||
non_data_space += inner_border * (column_count - 1)
|
||||
|
||||
# Space of all columns and their padding.
|
||||
data_space = sum(outer_widths)
|
||||
return data_space + non_data_space
|
5
tests/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
"""Allows importing from screenshot."""
|
||||
|
||||
import py
|
||||
|
||||
PROJECT_ROOT = py.path.local(__file__).dirpath().join('..')
|
292
tests/screenshot.py
Normal file
|
@ -0,0 +1,292 @@
|
|||
"""Take screenshots and search for subimages in images."""
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
import random
|
||||
import struct
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
try:
|
||||
from itertools import izip
|
||||
except ImportError:
|
||||
izip = zip # Py3
|
||||
|
||||
from tests import PROJECT_ROOT
|
||||
|
||||
STARTF_USESHOWWINDOW = getattr(subprocess, 'STARTF_USESHOWWINDOW', 1)
|
||||
STILL_ACTIVE = 259
|
||||
SW_MAXIMIZE = 3
|
||||
|
||||
|
||||
class StartupInfo(ctypes.Structure):
|
||||
"""STARTUPINFO structure."""
|
||||
|
||||
_fields_ = [
|
||||
('cb', ctypes.c_ulong),
|
||||
('lpReserved', ctypes.c_char_p),
|
||||
('lpDesktop', ctypes.c_char_p),
|
||||
('lpTitle', ctypes.c_char_p),
|
||||
('dwX', ctypes.c_ulong),
|
||||
('dwY', ctypes.c_ulong),
|
||||
('dwXSize', ctypes.c_ulong),
|
||||
('dwYSize', ctypes.c_ulong),
|
||||
('dwXCountChars', ctypes.c_ulong),
|
||||
('dwYCountChars', ctypes.c_ulong),
|
||||
('dwFillAttribute', ctypes.c_ulong),
|
||||
('dwFlags', ctypes.c_ulong),
|
||||
('wShowWindow', ctypes.c_ushort),
|
||||
('cbReserved2', ctypes.c_ushort),
|
||||
('lpReserved2', ctypes.c_char_p),
|
||||
('hStdInput', ctypes.c_ulong),
|
||||
('hStdOutput', ctypes.c_ulong),
|
||||
('hStdError', ctypes.c_ulong),
|
||||
]
|
||||
|
||||
def __init__(self, maximize=False, title=None):
|
||||
"""Constructor.
|
||||
|
||||
:param bool maximize: Start process in new console window, maximized.
|
||||
:param bytes title: Set new window title to this instead of exe path.
|
||||
"""
|
||||
super(StartupInfo, self).__init__()
|
||||
self.cb = ctypes.sizeof(self)
|
||||
if maximize:
|
||||
self.dwFlags |= STARTF_USESHOWWINDOW
|
||||
self.wShowWindow = SW_MAXIMIZE
|
||||
if title:
|
||||
self.lpTitle = ctypes.c_char_p(title)
|
||||
|
||||
|
||||
class ProcessInfo(ctypes.Structure):
|
||||
"""PROCESS_INFORMATION structure."""
|
||||
|
||||
_fields_ = [
|
||||
('hProcess', ctypes.c_void_p),
|
||||
('hThread', ctypes.c_void_p),
|
||||
('dwProcessId', ctypes.c_ulong),
|
||||
('dwThreadId', ctypes.c_ulong),
|
||||
]
|
||||
|
||||
|
||||
class RunNewConsole(object):
|
||||
"""Run the command in a new console window. Windows only. Use in a with statement.
|
||||
|
||||
subprocess sucks and really limits your access to the win32 API. Its implementation is half-assed. Using this so
|
||||
that STARTUPINFO.lpTitle actually works and STARTUPINFO.dwFillAttribute produce the expected result.
|
||||
"""
|
||||
|
||||
def __init__(self, command, maximized=False, title=None):
|
||||
"""Constructor.
|
||||
|
||||
:param iter command: Command to run.
|
||||
:param bool maximized: Start process in new console window, maximized.
|
||||
:param bytes title: Set new window title to this. Needed by user32.FindWindow.
|
||||
"""
|
||||
if title is None:
|
||||
title = 'pytest-{0}-{1}'.format(os.getpid(), random.randint(1000, 9999)).encode('ascii')
|
||||
self.startup_info = StartupInfo(maximize=maximized, title=title)
|
||||
self.process_info = ProcessInfo()
|
||||
self.command_str = subprocess.list2cmdline(command).encode('ascii')
|
||||
self._handles = list()
|
||||
self._kernel32 = ctypes.LibraryLoader(ctypes.WinDLL).kernel32
|
||||
self._kernel32.GetExitCodeProcess.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulong)]
|
||||
self._kernel32.GetExitCodeProcess.restype = ctypes.c_long
|
||||
|
||||
def __del__(self):
|
||||
"""Close win32 handles."""
|
||||
while self._handles:
|
||||
try:
|
||||
self._kernel32.CloseHandle(self._handles.pop(0)) # .pop() is thread safe.
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
def __enter__(self):
|
||||
"""Entering the `with` block. Runs the process."""
|
||||
if not self._kernel32.CreateProcessA(
|
||||
None, # lpApplicationName
|
||||
self.command_str, # lpCommandLine
|
||||
None, # lpProcessAttributes
|
||||
None, # lpThreadAttributes
|
||||
False, # bInheritHandles
|
||||
subprocess.CREATE_NEW_CONSOLE, # dwCreationFlags
|
||||
None, # lpEnvironment
|
||||
str(PROJECT_ROOT).encode('ascii'), # lpCurrentDirectory
|
||||
ctypes.byref(self.startup_info), # lpStartupInfo
|
||||
ctypes.byref(self.process_info) # lpProcessInformation
|
||||
):
|
||||
raise ctypes.WinError()
|
||||
|
||||
# Add handles added by the OS.
|
||||
self._handles.append(self.process_info.hProcess)
|
||||
self._handles.append(self.process_info.hThread)
|
||||
|
||||
# Get hWnd.
|
||||
self.hwnd = 0
|
||||
for _ in range(int(5 / 0.1)):
|
||||
# Takes time for console window to initialize.
|
||||
self.hwnd = ctypes.windll.user32.FindWindowA(None, self.startup_info.lpTitle)
|
||||
if self.hwnd:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
assert self.hwnd
|
||||
|
||||
# Return generator that yields window size/position.
|
||||
return self._iter_pos()
|
||||
|
||||
def __exit__(self, *_):
|
||||
"""Cleanup."""
|
||||
try:
|
||||
# Verify process exited 0.
|
||||
status = ctypes.c_ulong(STILL_ACTIVE)
|
||||
while status.value == STILL_ACTIVE:
|
||||
time.sleep(0.1)
|
||||
if not self._kernel32.GetExitCodeProcess(self.process_info.hProcess, ctypes.byref(status)):
|
||||
raise ctypes.WinError()
|
||||
assert status.value == 0
|
||||
finally:
|
||||
# Close handles.
|
||||
self.__del__()
|
||||
|
||||
def _iter_pos(self):
|
||||
"""Yield new console window's current position and dimensions.
|
||||
|
||||
:return: Yields region the new window is in (left, upper, right, lower).
|
||||
:rtype: tuple
|
||||
"""
|
||||
rect = ctypes.create_string_buffer(16) # To be written to by GetWindowRect. RECT structure.
|
||||
while ctypes.windll.user32.GetWindowRect(self.hwnd, rect):
|
||||
left, top, right, bottom = struct.unpack('llll', rect.raw)
|
||||
width, height = right - left, bottom - top
|
||||
assert width > 1
|
||||
assert height > 1
|
||||
yield left, top, right, bottom
|
||||
raise StopIteration
|
||||
|
||||
|
||||
def iter_rows(pil_image):
|
||||
"""Yield tuple of pixels for each row in the image.
|
||||
|
||||
itertools.izip in Python 2.x and zip in Python 3.x are writen in C. Much faster than anything else I've found
|
||||
written in pure Python.
|
||||
|
||||
From:
|
||||
http://stackoverflow.com/questions/1624883/alternative-way-to-split-a-list-into-groups-of-n/1625023#1625023
|
||||
|
||||
:param PIL.Image.Image pil_image: Image to read from.
|
||||
|
||||
:return: Yields rows.
|
||||
:rtype: tuple
|
||||
"""
|
||||
iterator = izip(*(iter(pil_image.getdata()),) * pil_image.width)
|
||||
for row in iterator:
|
||||
yield row
|
||||
|
||||
|
||||
def get_most_interesting_row(pil_image):
|
||||
"""Look for a row in the image that has the most unique pixels.
|
||||
|
||||
:param PIL.Image.Image pil_image: Image to read from.
|
||||
|
||||
:return: Row (tuple of pixel tuples), row as a set, first pixel tuple, y offset from top.
|
||||
:rtype: tuple
|
||||
"""
|
||||
final = (None, set(), None, None) # row, row_set, first_pixel, y_pos
|
||||
for y_pos, row in enumerate(iter_rows(pil_image)):
|
||||
row_set = set(row)
|
||||
if len(row_set) > len(final[1]):
|
||||
final = row, row_set, row[0], y_pos
|
||||
if len(row_set) == pil_image.width:
|
||||
break # Can't get bigger.
|
||||
return final
|
||||
|
||||
|
||||
def count_subimages(screenshot, subimg):
|
||||
"""Check how often subimg appears in the screenshot image.
|
||||
|
||||
:param PIL.Image.Image screenshot: Screen shot to search through.
|
||||
:param PIL.Image.Image subimg: Subimage to search for.
|
||||
|
||||
:return: Number of times subimg appears in the screenshot.
|
||||
:rtype: int
|
||||
"""
|
||||
# Get row to search for.
|
||||
si_pixels = list(subimg.getdata()) # Load entire subimg into memory.
|
||||
si_width = subimg.width
|
||||
si_height = subimg.height
|
||||
si_row, si_row_set, si_pixel, si_y = get_most_interesting_row(subimg)
|
||||
occurrences = 0
|
||||
|
||||
# Look for subimg row in screenshot, then crop and compare pixel arrays.
|
||||
for y_pos, row in enumerate(iter_rows(screenshot)):
|
||||
if si_row_set - set(row):
|
||||
continue # Some pixels not found.
|
||||
for x_pos in range(screenshot.width - si_width + 1):
|
||||
if row[x_pos] != si_pixel:
|
||||
continue # First pixel does not match.
|
||||
if row[x_pos:x_pos + si_width] != si_row:
|
||||
continue # Row does not match.
|
||||
# Found match for interesting row of subimg in screenshot.
|
||||
y_corrected = y_pos - si_y
|
||||
with screenshot.crop((x_pos, y_corrected, x_pos + si_width, y_corrected + si_height)) as cropped:
|
||||
if list(cropped.getdata()) == si_pixels:
|
||||
occurrences += 1
|
||||
|
||||
return occurrences
|
||||
|
||||
|
||||
def try_candidates(screenshot, subimg_candidates, expected_count):
|
||||
"""Call count_subimages() for each subimage candidate until.
|
||||
|
||||
If you get ImportError run "pip install pillow". Only OSX and Windows is supported.
|
||||
|
||||
:param PIL.Image.Image screenshot: Screen shot to search through.
|
||||
:param iter subimg_candidates: Subimage paths to look for. List of strings.
|
||||
:param int expected_count: Try until any a subimage candidate is found this many times.
|
||||
|
||||
:return: Number of times subimg appears in the screenshot.
|
||||
:rtype: int
|
||||
"""
|
||||
from PIL import Image
|
||||
count_found = 0
|
||||
|
||||
for subimg_path in subimg_candidates:
|
||||
with Image.open(subimg_path) as rgba_s:
|
||||
with rgba_s.convert(mode='RGB') as subimg:
|
||||
# Make sure subimage isn't too large.
|
||||
assert subimg.width < 256
|
||||
assert subimg.height < 256
|
||||
|
||||
# Count.
|
||||
count_found = count_subimages(screenshot, subimg)
|
||||
if count_found == expected_count:
|
||||
break # No need to try other candidates.
|
||||
|
||||
return count_found
|
||||
|
||||
|
||||
def screenshot_until_match(save_to, timeout, subimg_candidates, expected_count, gen):
|
||||
"""Take screenshots until one of the 'done' subimages is found. Image is saved when subimage found or at timeout.
|
||||
|
||||
If you get ImportError run "pip install pillow". Only OSX and Windows is supported.
|
||||
|
||||
:param str save_to: Save screenshot to this PNG file path when expected count found or timeout.
|
||||
:param int timeout: Give up after these many seconds.
|
||||
:param iter subimg_candidates: Subimage paths to look for. List of strings.
|
||||
:param int expected_count: Keep trying until any of subimg_candidates is found this many times.
|
||||
:param iter gen: Generator yielding window position and size to crop screenshot to.
|
||||
"""
|
||||
from PIL import ImageGrab
|
||||
assert save_to.endswith('.png')
|
||||
stop_after = time.time() + timeout
|
||||
|
||||
# Take screenshots until subimage is found.
|
||||
while True:
|
||||
with ImageGrab.grab(next(gen)) as rgba:
|
||||
with rgba.convert(mode='RGB') as screenshot:
|
||||
count_found = try_candidates(screenshot, subimg_candidates, expected_count)
|
||||
if count_found == expected_count or time.time() > stop_after:
|
||||
screenshot.save(save_to)
|
||||
assert count_found == expected_count
|
||||
return
|
||||
time.sleep(0.5)
|
1
tests/test_all_tables_e2e/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Allows importing from screenshot."""
|
BIN
tests/test_all_tables_e2e/sub_ascii_win10.bmp
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
tests/test_all_tables_e2e/sub_ascii_winxp.bmp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
tests/test_all_tables_e2e/sub_double_win10.bmp
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
tests/test_all_tables_e2e/sub_double_win10b.bmp
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
tests/test_all_tables_e2e/sub_double_winxp.bmp
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
tests/test_all_tables_e2e/sub_single_win10.bmp
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
tests/test_all_tables_e2e/sub_single_win10b.bmp
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
tests/test_all_tables_e2e/sub_single_winxp.bmp
Normal file
After Width: | Height: | Size: 45 KiB |
145
tests/test_all_tables_e2e/test_ascii_table.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
"""AsciiTable end to end testing."""
|
||||
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
||||
from terminaltables import AsciiTable
|
||||
from terminaltables.terminal_io import IS_WINDOWS
|
||||
from tests import PROJECT_ROOT
|
||||
from tests.screenshot import RunNewConsole, screenshot_until_match
|
||||
|
||||
HERE = py.path.local(__file__).dirpath()
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test single-lined cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green'],
|
||||
[],
|
||||
]
|
||||
table = AsciiTable(table_data, 'Example')
|
||||
table.inner_footing_row_border = True
|
||||
table.justify_columns[0] = 'left'
|
||||
table.justify_columns[1] = 'center'
|
||||
table.justify_columns[2] = 'right'
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+Example-----+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'+------------+-------+-----------+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'| Watermelon | green | |\n'
|
||||
'+------------+-------+-----------+\n'
|
||||
'| | | |\n'
|
||||
'+------------+-------+-----------+'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test multi-lined cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
table = AsciiTable(table_data)
|
||||
|
||||
# Test defaults.
|
||||
actual = table.table
|
||||
expected = (
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| Show | Characters |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n'
|
||||
'| | Dil Pickles |\n'
|
||||
'| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Test inner row border.
|
||||
table.inner_row_border = True
|
||||
actual = table.table
|
||||
expected = (
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| Show | Characters |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n'
|
||||
'| | Dil Pickles |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Justify right.
|
||||
table.justify_columns = {1: 'right'}
|
||||
actual = table.table
|
||||
expected = (
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| Show | Characters |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n'
|
||||
'| | Dil Pickles |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+\n'
|
||||
'| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |\n'
|
||||
'+------------+-------------------------------------------------------------------------------------+'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.skipif(str(not IS_WINDOWS))
|
||||
@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30
|
||||
def test_windows_screenshot(tmpdir):
|
||||
"""Test on Windows in a new console window. Take a screenshot to verify it works.
|
||||
|
||||
:param tmpdir: pytest fixture.
|
||||
"""
|
||||
script = tmpdir.join('script.py')
|
||||
command = [sys.executable, str(script)]
|
||||
screenshot = PROJECT_ROOT.join('test_ascii_table.png')
|
||||
if screenshot.check():
|
||||
screenshot.remove()
|
||||
|
||||
# Generate script.
|
||||
script_template = dedent(u"""\
|
||||
from __future__ import print_function
|
||||
import os, time
|
||||
from colorclass import Color, Windows
|
||||
from terminaltables import AsciiTable
|
||||
Windows.enable(auto_colors=True)
|
||||
stop_after = time.time() + 20
|
||||
|
||||
table_data = [
|
||||
[Color('{b}Name{/b}'), Color('{b}Color{/b}'), Color('{b}Misc{/b}')],
|
||||
['Avocado', Color('{autogreen}green{/fg}'), 100],
|
||||
['Tomato', Color('{autored}red{/fg}'), 0.5],
|
||||
['Lettuce', Color('{autogreen}green{/fg}'), None],
|
||||
]
|
||||
print(AsciiTable(table_data).table)
|
||||
|
||||
print('Waiting for screenshot_until_match()...')
|
||||
while not os.path.exists(r'%s') and time.time() < stop_after:
|
||||
time.sleep(0.5)
|
||||
""")
|
||||
script_contents = script_template % str(screenshot)
|
||||
script.write(script_contents.encode('utf-8'), mode='wb')
|
||||
|
||||
# Setup expected.
|
||||
sub_images = [str(p) for p in HERE.listdir('sub_ascii_*.bmp')]
|
||||
assert sub_images
|
||||
|
||||
# Run.
|
||||
with RunNewConsole(command) as gen:
|
||||
screenshot_until_match(str(screenshot), 15, sub_images, 1, gen)
|
245
tests/test_all_tables_e2e/test_double_table.py
Normal file
|
@ -0,0 +1,245 @@
|
|||
"""DoubleTable end to end testing."""
|
||||
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
||||
from terminaltables import DoubleTable
|
||||
from terminaltables.terminal_io import IS_WINDOWS
|
||||
from tests import PROJECT_ROOT
|
||||
from tests.screenshot import RunNewConsole, screenshot_until_match
|
||||
|
||||
HERE = py.path.local(__file__).dirpath()
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test single-lined cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green'],
|
||||
[],
|
||||
]
|
||||
table = DoubleTable(table_data, 'Example')
|
||||
table.inner_footing_row_border = True
|
||||
table.justify_columns[0] = 'left'
|
||||
table.justify_columns[1] = 'center'
|
||||
table.justify_columns[2] = 'right'
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
u'\u2554Example\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n'
|
||||
|
||||
u'\u2551 Name \u2551 Color \u2551 Type \u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 Avocado \u2551 green \u2551 nut \u2551\n'
|
||||
|
||||
u'\u2551 Tomato \u2551 red \u2551 fruit \u2551\n'
|
||||
|
||||
u'\u2551 Lettuce \u2551 green \u2551 vegetable \u2551\n'
|
||||
|
||||
u'\u2551 Watermelon \u2551 green \u2551 \u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 \u2551 \u2551 \u2551\n'
|
||||
|
||||
u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test multi-lined cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
table = DoubleTable(table_data)
|
||||
|
||||
# Test defaults.
|
||||
actual = table.table
|
||||
expected = (
|
||||
u'\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n'
|
||||
|
||||
u'\u2551 Show \u2551 Characters '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 Rugrats \u2551 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2551 \u2551 Dil Pickles '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2551 South Park \u2551 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Test inner row border.
|
||||
table.inner_row_border = True
|
||||
actual = table.table
|
||||
expected = (
|
||||
u'\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n'
|
||||
|
||||
u'\u2551 Show \u2551 Characters '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 Rugrats \u2551 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2551 \u2551 Dil Pickles '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 South Park \u2551 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Justify right.
|
||||
table.justify_columns = {1: 'right'}
|
||||
actual = table.table
|
||||
expected = (
|
||||
u'\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2566\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n'
|
||||
|
||||
u'\u2551 Show \u2551 Characters '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 Rugrats \u2551 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2551 \u2551 Dil Pickles '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256c\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n'
|
||||
|
||||
u'\u2551 South Park \u2551 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
u'\u2551\n'
|
||||
|
||||
u'\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550'
|
||||
u'\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.skipif(str(not IS_WINDOWS))
|
||||
@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30
|
||||
def test_windows_screenshot(tmpdir):
|
||||
"""Test on Windows in a new console window. Take a screenshot to verify it works.
|
||||
|
||||
:param tmpdir: pytest fixture.
|
||||
"""
|
||||
script = tmpdir.join('script.py')
|
||||
command = [sys.executable, str(script)]
|
||||
screenshot = PROJECT_ROOT.join('test_double_table.png')
|
||||
if screenshot.check():
|
||||
screenshot.remove()
|
||||
|
||||
# Generate script.
|
||||
script_template = dedent(u"""\
|
||||
from __future__ import print_function
|
||||
import os, time
|
||||
from colorclass import Color, Windows
|
||||
from terminaltables import DoubleTable
|
||||
Windows.enable(auto_colors=True)
|
||||
stop_after = time.time() + 20
|
||||
|
||||
table_data = [
|
||||
[Color('{b}Name{/b}'), Color('{b}Color{/b}'), Color('{b}Misc{/b}')],
|
||||
['Avocado', Color('{autogreen}green{/fg}'), 100],
|
||||
['Tomato', Color('{autored}red{/fg}'), 0.5],
|
||||
['Lettuce', Color('{autogreen}green{/fg}'), None],
|
||||
]
|
||||
print(DoubleTable(table_data).table)
|
||||
|
||||
print('Waiting for screenshot_until_match()...')
|
||||
while not os.path.exists(r'%s') and time.time() < stop_after:
|
||||
time.sleep(0.5)
|
||||
""")
|
||||
script_contents = script_template % str(screenshot)
|
||||
script.write(script_contents.encode('utf-8'), mode='wb')
|
||||
|
||||
# Setup expected.
|
||||
sub_images = [str(p) for p in HERE.listdir('sub_double_*.bmp')]
|
||||
assert sub_images
|
||||
|
||||
# Run.
|
||||
with RunNewConsole(command) as gen:
|
||||
screenshot_until_match(str(screenshot), 15, sub_images, 1, gen)
|
77
tests/test_all_tables_e2e/test_github_table.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""GithubFlavoredMarkdownTable end to end testing."""
|
||||
|
||||
from terminaltables import GithubFlavoredMarkdownTable
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test single-lined cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green'],
|
||||
[],
|
||||
]
|
||||
table = GithubFlavoredMarkdownTable(table_data)
|
||||
table.inner_footing_row_border = True
|
||||
table.justify_columns[0] = 'left'
|
||||
table.justify_columns[1] = 'center'
|
||||
table.justify_columns[2] = 'right'
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'| Name | Color | Type |\n'
|
||||
'|:-----------|:-----:|----------:|\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'| Watermelon | green | |\n'
|
||||
'| | | |'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test multi-lined cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
table = GithubFlavoredMarkdownTable(table_data)
|
||||
|
||||
# Test defaults.
|
||||
actual = table.table
|
||||
expected = (
|
||||
'| Show | Characters |\n'
|
||||
'|------------|-------------------------------------------------------------------------------------|\n'
|
||||
'| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n'
|
||||
'| | Dil Pickles |\n'
|
||||
'| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Test inner row border.
|
||||
table.inner_row_border = True
|
||||
actual = table.table
|
||||
expected = (
|
||||
'| Show | Characters |\n'
|
||||
'|------------|-------------------------------------------------------------------------------------|\n'
|
||||
'| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n'
|
||||
'| | Dil Pickles |\n'
|
||||
'| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Justify right.
|
||||
table.justify_columns = {1: 'right'}
|
||||
actual = table.table
|
||||
expected = (
|
||||
'| Show | Characters |\n'
|
||||
'|------------|------------------------------------------------------------------------------------:|\n'
|
||||
'| Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, |\n'
|
||||
'| | Dil Pickles |\n'
|
||||
'| South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick |'
|
||||
)
|
||||
assert actual == expected
|
59
tests/test_all_tables_e2e/test_porcelain_table.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
"""PorcelainTable end to end testing."""
|
||||
|
||||
from terminaltables import PorcelainTable
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test single-lined cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green']
|
||||
]
|
||||
table = PorcelainTable(table_data)
|
||||
table.justify_columns[0] = 'left'
|
||||
table.justify_columns[1] = 'center'
|
||||
table.justify_columns[2] = 'right'
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
' Name | Color | Type \n'
|
||||
' Avocado | green | nut \n'
|
||||
' Tomato | red | fruit \n'
|
||||
' Lettuce | green | vegetable \n'
|
||||
' Watermelon | green | '
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test multi-lined cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
table = PorcelainTable(table_data)
|
||||
|
||||
# Test defaults.
|
||||
actual = table.table
|
||||
expected = (
|
||||
' Show | Characters \n'
|
||||
' Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, \n'
|
||||
' | Dil Pickles \n'
|
||||
' South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Justify right.
|
||||
table.justify_columns = {1: 'right'}
|
||||
actual = table.table
|
||||
expected = (
|
||||
' Show | Characters \n'
|
||||
' Rugrats | Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, \n'
|
||||
' | Dil Pickles \n'
|
||||
' South Park | Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
)
|
||||
assert actual == expected
|
171
tests/test_all_tables_e2e/test_single_table.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
"""SingleTable end to end testing on Linux/OSX."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables import SingleTable
|
||||
from terminaltables.terminal_io import IS_WINDOWS
|
||||
|
||||
pytestmark = pytest.mark.skipif(str(IS_WINDOWS))
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test single-lined cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green'],
|
||||
[],
|
||||
]
|
||||
table = SingleTable(table_data, 'Example')
|
||||
table.inner_footing_row_border = True
|
||||
table.justify_columns[0] = 'left'
|
||||
table.justify_columns[1] = 'center'
|
||||
table.justify_columns[2] = 'right'
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'\033(0\x6c\033(BExample\033(0\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x6b\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Name \033(0\x78\033(B Color \033(0\x78\033(B Type \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Avocado \033(0\x78\033(B green \033(0\x78\033(B nut \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Tomato \033(0\x78\033(B red \033(0\x78\033(B fruit \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Lettuce \033(0\x78\033(B green \033(0\x78\033(B vegetable \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Watermelon \033(0\x78\033(B green \033(0\x78\033(B \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B \033(0\x78\033(B \033(0\x78\033(B \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x6a\033(B'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test multi-lined cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
table = SingleTable(table_data)
|
||||
|
||||
# Test defaults.
|
||||
actual = table.table
|
||||
expected = (
|
||||
'\033(0\x6c\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6b\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Show \033(0\x78\033(B Characters '
|
||||
' \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Rugrats \033(0\x78\033(B Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille,'
|
||||
' Angelica Pickles, \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B \033(0\x78\033(B Dil Pickles '
|
||||
' \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B South Park \033(0\x78\033(B Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
' \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6a\033(B'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Test inner row border.
|
||||
table.inner_row_border = True
|
||||
actual = table.table
|
||||
expected = (
|
||||
'\033(0\x6c\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6b\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Show \033(0\x78\033(B Characters '
|
||||
' \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Rugrats \033(0\x78\033(B Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille,'
|
||||
' Angelica Pickles, \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B \033(0\x78\033(B Dil Pickles '
|
||||
' \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B South Park \033(0\x78\033(B Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
' \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6a\033(B'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Justify right.
|
||||
table.justify_columns = {1: 'right'}
|
||||
actual = table.table
|
||||
expected = (
|
||||
'\033(0\x6c\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x77\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6b\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Show \033(0\x78\033(B '
|
||||
' Characters \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B Rugrats \033(0\x78\033(B Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille,'
|
||||
' Angelica Pickles, \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B \033(0\x78\033(B '
|
||||
' Dil Pickles \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x74\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6e\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x75\033(B\n'
|
||||
|
||||
'\033(0\x78\033(B South Park \033(0\x78\033(B Stan Marsh, Kyle Broflovski, '
|
||||
'Eric Cartman, Kenny McCormick \033(0\x78\033(B\n'
|
||||
|
||||
'\033(0\x6d\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x76\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71'
|
||||
'\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x6a\033(B'
|
||||
)
|
||||
assert actual == expected
|
246
tests/test_all_tables_e2e/test_single_table_windows.py
Normal file
|
@ -0,0 +1,246 @@
|
|||
"""SingleTable end to end testing on Windows."""
|
||||
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
||||
from terminaltables import SingleTable
|
||||
from terminaltables.terminal_io import IS_WINDOWS
|
||||
from tests import PROJECT_ROOT
|
||||
from tests.screenshot import RunNewConsole, screenshot_until_match
|
||||
|
||||
HERE = py.path.local(__file__).dirpath()
|
||||
pytestmark = pytest.mark.skipif(str(not IS_WINDOWS))
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test single-lined cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green'],
|
||||
[],
|
||||
]
|
||||
table = SingleTable(table_data, 'Example')
|
||||
table.inner_footing_row_border = True
|
||||
table.justify_columns[0] = 'left'
|
||||
table.justify_columns[1] = 'center'
|
||||
table.justify_columns[2] = 'right'
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
u'\u250cExample\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n'
|
||||
|
||||
u'\u2502 Name \u2502 Color \u2502 Type \u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 Avocado \u2502 green \u2502 nut \u2502\n'
|
||||
|
||||
u'\u2502 Tomato \u2502 red \u2502 fruit \u2502\n'
|
||||
|
||||
u'\u2502 Lettuce \u2502 green \u2502 vegetable \u2502\n'
|
||||
|
||||
u'\u2502 Watermelon \u2502 green \u2502 \u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 \u2502 \u2502 \u2502\n'
|
||||
|
||||
u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test multi-lined cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
table = SingleTable(table_data)
|
||||
|
||||
# Test defaults.
|
||||
actual = table.table
|
||||
expected = (
|
||||
u'\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n'
|
||||
|
||||
u'\u2502 Show \u2502 Characters '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 Rugrats \u2502 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2502 \u2502 Dil Pickles '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2502 South Park \u2502 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Test inner row border.
|
||||
table.inner_row_border = True
|
||||
actual = table.table
|
||||
expected = (
|
||||
u'\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n'
|
||||
|
||||
u'\u2502 Show \u2502 Characters '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 Rugrats \u2502 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2502 \u2502 Dil Pickles '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 South Park \u2502 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
# Justify right.
|
||||
table.justify_columns = {1: 'right'}
|
||||
actual = table.table
|
||||
expected = (
|
||||
u'\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n'
|
||||
|
||||
u'\u2502 Show \u2502 Characters '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 Rugrats \u2502 Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles, '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2502 \u2502 Dil Pickles '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n'
|
||||
|
||||
u'\u2502 South Park \u2502 Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick '
|
||||
u'\u2502\n'
|
||||
|
||||
u'\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
||||
u'\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518'
|
||||
)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.skipif(str(not IS_WINDOWS))
|
||||
@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30
|
||||
def test_windows_screenshot(tmpdir):
|
||||
"""Test on Windows in a new console window. Take a screenshot to verify it works.
|
||||
|
||||
:param tmpdir: pytest fixture.
|
||||
"""
|
||||
script = tmpdir.join('script.py')
|
||||
command = [sys.executable, str(script)]
|
||||
screenshot = PROJECT_ROOT.join('test_single_table.png')
|
||||
if screenshot.check():
|
||||
screenshot.remove()
|
||||
|
||||
# Generate script.
|
||||
script_template = dedent(u"""\
|
||||
from __future__ import print_function
|
||||
import os, time
|
||||
from colorclass import Color, Windows
|
||||
from terminaltables import SingleTable
|
||||
Windows.enable(auto_colors=True)
|
||||
stop_after = time.time() + 20
|
||||
|
||||
table_data = [
|
||||
[Color('{b}Name{/b}'), Color('{b}Color{/b}'), Color('{b}Misc{/b}')],
|
||||
['Avocado', Color('{autogreen}green{/fg}'), 100],
|
||||
['Tomato', Color('{autored}red{/fg}'), 0.5],
|
||||
['Lettuce', Color('{autogreen}green{/fg}'), None],
|
||||
]
|
||||
print(SingleTable(table_data).table)
|
||||
|
||||
print('Waiting for screenshot_until_match()...')
|
||||
while not os.path.exists(r'%s') and time.time() < stop_after:
|
||||
time.sleep(0.5)
|
||||
""")
|
||||
script_contents = script_template % str(screenshot)
|
||||
script.write(script_contents.encode('utf-8'), mode='wb')
|
||||
|
||||
# Setup expected.
|
||||
sub_images = [str(p) for p in HERE.listdir('sub_single_*.bmp')]
|
||||
assert sub_images
|
||||
|
||||
# Run.
|
||||
with RunNewConsole(command) as gen:
|
||||
screenshot_until_match(str(screenshot), 15, sub_images, 1, gen)
|
108
tests/test_ascii_table.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
"""Test AsciiTable class."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.other_tables import AsciiTable
|
||||
|
||||
SINGLE_LINE = (
|
||||
('Name', 'Color', 'Type'),
|
||||
('Avocado', 'green', 'nut'),
|
||||
('Tomato', 'red', 'fruit'),
|
||||
('Lettuce', 'green', 'vegetable'),
|
||||
)
|
||||
|
||||
MULTI_LINE = (
|
||||
('Show', 'Characters'),
|
||||
('Rugrats', 'Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\nDil Pickles'),
|
||||
('South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick'),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch(monkeypatch):
|
||||
"""Monkeypatch before every test function in this module.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
"""
|
||||
monkeypatch.setattr('terminaltables.ascii_table.terminal_size', lambda: (79, 24))
|
||||
monkeypatch.setattr('terminaltables.width_and_alignment.terminal_size', lambda: (79, 24))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('table_data,column_number,expected', [
|
||||
([], 0, IndexError),
|
||||
([[]], 0, IndexError),
|
||||
([['']], 1, IndexError),
|
||||
(SINGLE_LINE, 0, 55),
|
||||
(SINGLE_LINE, 1, 53),
|
||||
(SINGLE_LINE, 2, 57),
|
||||
(MULTI_LINE, 0, -11),
|
||||
(MULTI_LINE, 1, 62),
|
||||
])
|
||||
def test_column_max_width(table_data, column_number, expected):
|
||||
"""Test method in class.
|
||||
|
||||
:param iter table_data: Passed to AsciiTable.__init__().
|
||||
:param int column_number: Passed to AsciiTable.column_max_width().
|
||||
:param int expected: Expected return value of AsciiTable.column_max_width().
|
||||
"""
|
||||
table = AsciiTable(table_data)
|
||||
|
||||
if expected == IndexError:
|
||||
with pytest.raises(IndexError):
|
||||
table.column_max_width(column_number)
|
||||
return
|
||||
|
||||
actual = table.column_max_width(column_number)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_column_widths():
|
||||
"""Test method in class."""
|
||||
assert AsciiTable([]).column_widths == list()
|
||||
|
||||
table = AsciiTable(SINGLE_LINE)
|
||||
actual = table.column_widths
|
||||
assert actual == [7, 5, 9]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('table_data,terminal_width,expected', [
|
||||
([], None, True),
|
||||
([[]], None, True),
|
||||
([['']], None, True),
|
||||
(SINGLE_LINE, None, True),
|
||||
(SINGLE_LINE, 30, False),
|
||||
(MULTI_LINE, None, False),
|
||||
(MULTI_LINE, 100, True),
|
||||
])
|
||||
def test_ok(monkeypatch, table_data, terminal_width, expected):
|
||||
"""Test method in class.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
:param iter table_data: Passed to AsciiTable.__init__().
|
||||
:param int terminal_width: Monkeypatch width of terminal_size() if not None.
|
||||
:param bool expected: Expected return value.
|
||||
"""
|
||||
if terminal_width is not None:
|
||||
monkeypatch.setattr('terminaltables.ascii_table.terminal_size', lambda: (terminal_width, 24))
|
||||
table = AsciiTable(table_data)
|
||||
actual = table.ok
|
||||
assert actual is expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('table_data,expected', [
|
||||
([], 2),
|
||||
([[]], 2),
|
||||
([['']], 4),
|
||||
([[' ']], 5),
|
||||
(SINGLE_LINE, 31),
|
||||
(MULTI_LINE, 100),
|
||||
])
|
||||
def test_table_width(table_data, expected):
|
||||
"""Test method in class.
|
||||
|
||||
:param iter table_data: Passed to AsciiTable.__init__().
|
||||
:param int expected: Expected return value.
|
||||
"""
|
||||
table = AsciiTable(table_data)
|
||||
actual = table.table_width
|
||||
assert actual == expected
|
86
tests/test_base_table/test_gen_row_lines.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
"""Test method in BaseTable class."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.base_table import BaseTable
|
||||
|
||||
|
||||
@pytest.mark.parametrize('style', ['heading', 'footing', 'row'])
|
||||
def test_single_line(style):
|
||||
"""Test with single-line row.
|
||||
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
row = ['Row One Column One', 'Two', 'Three']
|
||||
table = BaseTable([row])
|
||||
actual = [tuple(i) for i in table.gen_row_lines(row, style, [18, 3, 5], 1)]
|
||||
expected = [
|
||||
('|', ' Row One Column One ', '|', ' Two ', '|', ' Three ', '|'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('style', ['heading', 'footing', 'row'])
|
||||
def test_multi_line(style):
|
||||
"""Test with multi-line row.
|
||||
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
row = ['Row One\nColumn One', 'Two', 'Three']
|
||||
table = BaseTable([row])
|
||||
actual = [tuple(i) for i in table.gen_row_lines(row, style, [10, 3, 5], 2)]
|
||||
expected = [
|
||||
('|', ' Row One ', '|', ' Two ', '|', ' Three ', '|'),
|
||||
('|', ' Column One ', '|', ' ', '|', ' ', '|'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('style', ['heading', 'footing', 'row'])
|
||||
def test_no_padding_no_borders(style):
|
||||
"""Test without padding or borders.
|
||||
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
row = ['Row One\nColumn One', 'Two', 'Three']
|
||||
table = BaseTable([row])
|
||||
table.inner_column_border = False
|
||||
table.outer_border = False
|
||||
table.padding_left = 0
|
||||
table.padding_right = 0
|
||||
actual = [tuple(i) for i in table.gen_row_lines(row, style, [10, 3, 5], 2)]
|
||||
expected = [
|
||||
('Row One ', 'Two', 'Three'),
|
||||
('Column One', ' ', ' '),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('style', ['heading', 'footing', 'row'])
|
||||
def test_uneven(style):
|
||||
"""Test with row missing cells.
|
||||
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
row = ['Row One Column One']
|
||||
table = BaseTable([row])
|
||||
actual = [tuple(i) for i in table.gen_row_lines(row, style, [18, 3, 5], 1)]
|
||||
expected = [
|
||||
('|', ' Row One Column One ', '|', ' ', '|', ' ', '|'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('style', ['heading', 'footing', 'row'])
|
||||
def test_empty_table(style):
|
||||
"""Test empty table.
|
||||
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
row = []
|
||||
table = BaseTable([row])
|
||||
actual = [tuple(i) for i in table.gen_row_lines(row, style, [], 0)]
|
||||
expected = [
|
||||
('|', '|'),
|
||||
]
|
||||
assert actual == expected
|
225
tests/test_base_table/test_gen_table.py
Normal file
|
@ -0,0 +1,225 @@
|
|||
"""Test method in BaseTable class."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.base_table import BaseTable
|
||||
from terminaltables.build import flatten
|
||||
from terminaltables.width_and_alignment import max_dimensions
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inner_heading_row_border', [True, False])
|
||||
@pytest.mark.parametrize('inner_footing_row_border', [True, False])
|
||||
@pytest.mark.parametrize('inner_row_border', [True, False])
|
||||
def test_inner_row_borders(inner_heading_row_border, inner_footing_row_border, inner_row_border):
|
||||
"""Test heading/footing/row borders.
|
||||
|
||||
:param bool inner_heading_row_border: Passed to table.
|
||||
:param bool inner_footing_row_border: Passed to table.
|
||||
:param bool inner_row_border: Passed to table.
|
||||
"""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
table = BaseTable(table_data)
|
||||
table.inner_heading_row_border = inner_heading_row_border
|
||||
table.inner_footing_row_border = inner_footing_row_border
|
||||
table.inner_row_border = inner_row_border
|
||||
inner_widths, inner_heights, outer_widths = max_dimensions(table_data, table.padding_left, table.padding_right)[:3]
|
||||
actual = flatten(table.gen_table(inner_widths, inner_heights, outer_widths))
|
||||
|
||||
# Determine expected.
|
||||
if inner_row_border:
|
||||
expected = (
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
elif inner_heading_row_border and inner_footing_row_border:
|
||||
expected = (
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
elif inner_heading_row_border:
|
||||
expected = (
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
elif inner_footing_row_border:
|
||||
expected = (
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_border', [True, False])
|
||||
def test_outer_borders(outer_border):
|
||||
"""Test left/right/top/bottom table borders.
|
||||
|
||||
:param bool outer_border: Passed to table.
|
||||
"""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
table = BaseTable(table_data, 'Example Table')
|
||||
table.outer_border = outer_border
|
||||
inner_widths, inner_heights, outer_widths = max_dimensions(table_data, table.padding_left, table.padding_right)[:3]
|
||||
actual = flatten(table.gen_table(inner_widths, inner_heights, outer_widths))
|
||||
|
||||
# Determine expected.
|
||||
if outer_border:
|
||||
expected = (
|
||||
'+Example Table----+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
' Name | Color | Type \n'
|
||||
'---------+-------+-----------\n'
|
||||
' Avocado | green | nut \n'
|
||||
' Tomato | red | fruit \n'
|
||||
' Lettuce | green | vegetable '
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mode', ['row', 'one', 'blank', 'empty', 'none'])
|
||||
@pytest.mark.parametrize('bare', [False, True])
|
||||
def test_one_no_rows(mode, bare):
|
||||
"""Test with one or no rows.
|
||||
|
||||
:param str mode: Type of table contents to test.
|
||||
:param bool bare: Disable padding/borders.
|
||||
"""
|
||||
if mode == 'row':
|
||||
table_data = [
|
||||
['Avocado', 'green', 'nut'],
|
||||
]
|
||||
elif mode == 'one':
|
||||
table_data = [
|
||||
['Avocado'],
|
||||
]
|
||||
elif mode == 'blank':
|
||||
table_data = [
|
||||
[''],
|
||||
]
|
||||
elif mode == 'empty':
|
||||
table_data = [
|
||||
[],
|
||||
]
|
||||
else:
|
||||
table_data = [
|
||||
]
|
||||
table = BaseTable(table_data)
|
||||
if bare:
|
||||
table.inner_column_border = False
|
||||
table.inner_footing_row_border = False
|
||||
table.inner_heading_row_border = False
|
||||
table.inner_row_border = False
|
||||
table.outer_border = False
|
||||
table.padding_left = 0
|
||||
table.padding_right = 0
|
||||
inner_widths, inner_heights, outer_widths = max_dimensions(table_data, table.padding_left, table.padding_right)[:3]
|
||||
actual = flatten(table.gen_table(inner_widths, inner_heights, outer_widths))
|
||||
|
||||
# Determine expected.
|
||||
if mode == 'row':
|
||||
if bare:
|
||||
expected = (
|
||||
'Avocadogreennut'
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
'+---------+-------+-----+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'+---------+-------+-----+'
|
||||
)
|
||||
elif mode == 'one':
|
||||
if bare:
|
||||
expected = (
|
||||
'Avocado'
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
'+---------+\n'
|
||||
'| Avocado |\n'
|
||||
'+---------+'
|
||||
)
|
||||
elif mode == 'blank': # Remember there's still padding.
|
||||
if bare:
|
||||
expected = (
|
||||
''
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
'+--+\n'
|
||||
'| |\n'
|
||||
'+--+'
|
||||
)
|
||||
elif mode == 'empty':
|
||||
if bare:
|
||||
expected = (
|
||||
''
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
'++\n'
|
||||
'||\n'
|
||||
'++'
|
||||
)
|
||||
else:
|
||||
if bare:
|
||||
expected = (
|
||||
''
|
||||
)
|
||||
else:
|
||||
expected = (
|
||||
'++\n'
|
||||
'++'
|
||||
)
|
||||
|
||||
assert actual == expected
|
98
tests/test_base_table/test_horizontal_border.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
"""Test method in BaseTable class."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.base_table import BaseTable
|
||||
from terminaltables.width_and_alignment import max_dimensions
|
||||
|
||||
SINGLE_LINE = (
|
||||
('Name', 'Color', 'Type'),
|
||||
('Avocado', 'green', 'nut'),
|
||||
('Tomato', 'red', 'fruit'),
|
||||
('Lettuce', 'green', 'vegetable'),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inner_column_border', [True, False])
|
||||
@pytest.mark.parametrize('style', ['top', 'bottom'])
|
||||
def test_top_bottom(inner_column_border, style):
|
||||
"""Test top and bottom borders.
|
||||
|
||||
:param bool inner_column_border: Passed to table class.
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
table = BaseTable(SINGLE_LINE, 'Example')
|
||||
table.inner_column_border = inner_column_border
|
||||
outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2]
|
||||
|
||||
# Determine expected.
|
||||
if style == 'top' and inner_column_border:
|
||||
expected = '+Example--+-------+-----------+'
|
||||
elif style == 'top':
|
||||
expected = '+Example--------------------+'
|
||||
elif style == 'bottom' and inner_column_border:
|
||||
expected = '+---------+-------+-----------+'
|
||||
else:
|
||||
expected = '+---------------------------+'
|
||||
|
||||
# Test.
|
||||
actual = ''.join(table.horizontal_border(style, outer_widths))
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inner_column_border', [True, False])
|
||||
@pytest.mark.parametrize('outer_border', [True, False])
|
||||
@pytest.mark.parametrize('style', ['heading', 'footing'])
|
||||
def test_heading_footing(inner_column_border, outer_border, style):
|
||||
"""Test heading and footing borders.
|
||||
|
||||
:param bool inner_column_border: Passed to table class.
|
||||
:param bool outer_border: Passed to table class.
|
||||
:param str style: Passed to method.
|
||||
"""
|
||||
table = BaseTable(SINGLE_LINE)
|
||||
table.inner_column_border = inner_column_border
|
||||
table.outer_border = outer_border
|
||||
outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2]
|
||||
|
||||
# Determine expected.
|
||||
if style == 'heading' and outer_border:
|
||||
expected = '+---------+-------+-----------+' if inner_column_border else '+---------------------------+'
|
||||
elif style == 'heading':
|
||||
expected = '---------+-------+-----------' if inner_column_border else '---------------------------'
|
||||
elif style == 'footing' and outer_border:
|
||||
expected = '+---------+-------+-----------+' if inner_column_border else '+---------------------------+'
|
||||
else:
|
||||
expected = '---------+-------+-----------' if inner_column_border else '---------------------------'
|
||||
|
||||
# Test.
|
||||
actual = ''.join(table.horizontal_border(style, outer_widths))
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inner_column_border', [True, False])
|
||||
@pytest.mark.parametrize('outer_border', [True, False])
|
||||
def test_row(inner_column_border, outer_border):
|
||||
"""Test inner borders.
|
||||
|
||||
:param bool inner_column_border: Passed to table class.
|
||||
:param bool outer_border: Passed to table class.
|
||||
"""
|
||||
table = BaseTable(SINGLE_LINE)
|
||||
table.inner_column_border = inner_column_border
|
||||
table.outer_border = outer_border
|
||||
outer_widths = max_dimensions(table.table_data, table.padding_left, table.padding_right)[2]
|
||||
|
||||
# Determine expected.
|
||||
if inner_column_border and outer_border:
|
||||
expected = '+---------+-------+-----------+'
|
||||
elif inner_column_border:
|
||||
expected = '---------+-------+-----------'
|
||||
elif outer_border:
|
||||
expected = '+---------------------------+'
|
||||
else:
|
||||
expected = '---------------------------'
|
||||
|
||||
# Test.
|
||||
actual = ''.join(table.horizontal_border('row', outer_widths))
|
||||
assert actual == expected
|
196
tests/test_base_table/test_table.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
# coding: utf-8
|
||||
"""Test property in BaseTable class."""
|
||||
|
||||
from colorama import Fore
|
||||
from colorclass import Color
|
||||
from termcolor import colored
|
||||
|
||||
from terminaltables.base_table import BaseTable
|
||||
|
||||
|
||||
def test_ascii():
|
||||
"""Test with ASCII characters."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
table = BaseTable(table_data)
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Name | Color | Type |\n'
|
||||
'+---------+-------+-----------+\n'
|
||||
'| Avocado | green | nut |\n'
|
||||
'| Tomato | red | fruit |\n'
|
||||
'| Lettuce | green | vegetable |\n'
|
||||
'+---------+-------+-----------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_int():
|
||||
"""Test with integers instead of strings."""
|
||||
table_data = [
|
||||
[100, 10, 1],
|
||||
[0, 3, 6],
|
||||
[1, 4, 7],
|
||||
[2, 5, 8],
|
||||
]
|
||||
table = BaseTable(table_data, 1234567890)
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+1234567890+---+\n'
|
||||
'| 100 | 10 | 1 |\n'
|
||||
'+-----+----+---+\n'
|
||||
'| 0 | 3 | 6 |\n'
|
||||
'| 1 | 4 | 7 |\n'
|
||||
'| 2 | 5 | 8 |\n'
|
||||
'+-----+----+---+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_float():
|
||||
"""Test with floats instead of strings."""
|
||||
table_data = [
|
||||
[1.0, 22.0, 333.0],
|
||||
[0.1, 3.1, 6.1],
|
||||
[1.1, 4.1, 7.1],
|
||||
[2.1, 5.1, 8.1],
|
||||
]
|
||||
table = BaseTable(table_data, 0.12345678)
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+0.12345678--+-------+\n'
|
||||
'| 1.0 | 22.0 | 333.0 |\n'
|
||||
'+-----+------+-------+\n'
|
||||
'| 0.1 | 3.1 | 6.1 |\n'
|
||||
'| 1.1 | 4.1 | 7.1 |\n'
|
||||
'| 2.1 | 5.1 | 8.1 |\n'
|
||||
'+-----+------+-------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_bool_none():
|
||||
"""Test with NoneType/boolean instead of strings."""
|
||||
table_data = [
|
||||
[True, False, None],
|
||||
[True, False, None],
|
||||
[False, None, True],
|
||||
[None, True, False],
|
||||
]
|
||||
table = BaseTable(table_data, True)
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+True---+-------+-------+\n'
|
||||
'| True | False | None |\n'
|
||||
'+-------+-------+-------+\n'
|
||||
'| True | False | None |\n'
|
||||
'| False | None | True |\n'
|
||||
'| None | True | False |\n'
|
||||
'+-------+-------+-------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_cjk():
|
||||
"""Test with CJK characters."""
|
||||
table_data = [
|
||||
['CJK'],
|
||||
['蓝色'],
|
||||
['世界你好'],
|
||||
]
|
||||
table = BaseTable(table_data)
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+----------+\n'
|
||||
'| CJK |\n'
|
||||
'+----------+\n'
|
||||
'| 蓝色 |\n'
|
||||
'| 世界你好 |\n'
|
||||
'+----------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_rtl():
|
||||
"""Test with RTL characters."""
|
||||
table_data = [
|
||||
['RTL'],
|
||||
['שלום'],
|
||||
['معرب'],
|
||||
]
|
||||
table = BaseTable(table_data)
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+------+\n'
|
||||
'| RTL |\n'
|
||||
'+------+\n'
|
||||
'| שלום |\n'
|
||||
'| معرب |\n'
|
||||
'+------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_rtl_large():
|
||||
"""Test large table of RTL characters."""
|
||||
table_data = [
|
||||
['اكتب', 'اللون', 'اسم'],
|
||||
['البندق', 'أخضر', 'أفوكادو'],
|
||||
['ثمرة', 'أحمر', 'بندورة'],
|
||||
['الخضروات', 'أخضر', 'الخس'],
|
||||
]
|
||||
table = BaseTable(table_data, 'جوجل المترجم')
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
'+جوجل المترجم------+---------+\n'
|
||||
'| اكتب | اللون | اسم |\n'
|
||||
'+----------+-------+---------+\n'
|
||||
'| البندق | أخضر | أفوكادو |\n'
|
||||
'| ثمرة | أحمر | بندورة |\n'
|
||||
'| الخضروات | أخضر | الخس |\n'
|
||||
'+----------+-------+---------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_color():
|
||||
"""Test with color characters."""
|
||||
table_data = [
|
||||
['ansi', '\033[31mRed\033[39m', '\033[32mGreen\033[39m', '\033[34mBlue\033[39m'],
|
||||
['colorclass', Color('{red}Red{/red}'), Color('{green}Green{/green}'), Color('{blue}Blue{/blue}')],
|
||||
['colorama', Fore.RED + 'Red' + Fore.RESET, Fore.GREEN + 'Green' + Fore.RESET, Fore.BLUE + 'Blue' + Fore.RESET],
|
||||
['termcolor', colored('Red', 'red'), colored('Green', 'green'), colored('Blue', 'blue')],
|
||||
]
|
||||
table = BaseTable(table_data)
|
||||
table.inner_heading_row_border = False
|
||||
actual = table.table
|
||||
|
||||
expected = (
|
||||
u'+------------+-----+-------+------+\n'
|
||||
u'| ansi | \033[31mRed\033[39m | \033[32mGreen\033[39m | \033[34mBlue\033[39m |\n'
|
||||
u'| colorclass | \033[31mRed\033[39m | \033[32mGreen\033[39m | \033[34mBlue\033[39m |\n'
|
||||
u'| colorama | \033[31mRed\033[39m | \033[32mGreen\033[39m | \033[34mBlue\033[39m |\n'
|
||||
u'| termcolor | \033[31mRed\033[0m | \033[32mGreen\033[0m | \033[34mBlue\033[0m |\n'
|
||||
u'+------------+-----+-------+------+'
|
||||
)
|
||||
|
||||
assert actual == expected
|
312
tests/test_build/test_build_border.py
Normal file
|
@ -0,0 +1,312 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
from colorama import Fore, Style
|
||||
from colorclass import Color
|
||||
from termcolor import colored
|
||||
|
||||
from terminaltables.build import build_border
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,horizontal,left,intersect,right,expected', [
|
||||
([5, 6, 7], '-', '<', '+', '>', '<-----+------+------->'),
|
||||
([1, 1, 1], '-', '', '', '', '---'),
|
||||
([1, 1, 1], '', '', '', '', ''),
|
||||
([1], '-', '<', '+', '>', '<->'),
|
||||
([], '-', '<', '+', '>', '<>'),
|
||||
])
|
||||
def test_no_title(outer_widths, horizontal, left, intersect, right, expected):
|
||||
"""Test without title.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths with padding.
|
||||
:param str horizontal: Character to stretch across each column.
|
||||
:param str left: Left border.
|
||||
:param str intersect: Column separator.
|
||||
:param str right: Right border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
actual = build_border(outer_widths, horizontal, left, intersect, right)
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,intersect,expected', [
|
||||
([20], '+', 'Applications--------'),
|
||||
([20], '', 'Applications--------'),
|
||||
|
||||
([15, 5], '+', 'Applications---+-----'),
|
||||
([15, 5], '', 'Applications--------'),
|
||||
|
||||
([12], '+', 'Applications'),
|
||||
([12], '', 'Applications'),
|
||||
|
||||
([12, 1], '+', 'Applications+-'),
|
||||
([12, 1], '', 'Applications-'),
|
||||
|
||||
([12, 0], '+', 'Applications+'),
|
||||
([12, 0], '', 'Applications'),
|
||||
])
|
||||
@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')])
|
||||
def test_first_column_fit(outer_widths, left, intersect, right, expected):
|
||||
"""Test with title that fits in the first column.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths with padding.
|
||||
:param str left: Left border.
|
||||
:param str intersect: Column separator.
|
||||
:param str right: Right border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
if left and right:
|
||||
expected = left + expected + right
|
||||
actual = build_border(outer_widths, '-', left, intersect, right, title='Applications')
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,expected', [
|
||||
([20], 'Applications--------'),
|
||||
([10, 10], 'Applications--------'),
|
||||
([5, 5, 5, 5], 'Applications--------'),
|
||||
([3, 2, 3, 2, 3, 2, 3, 2], 'Applications--------'),
|
||||
([1] * 20, 'Applications--------'),
|
||||
([10, 5], 'Applications---'),
|
||||
([9, 5], 'Applications--'),
|
||||
([8, 5], 'Applications-'),
|
||||
([7, 5], 'Applications'),
|
||||
([6, 5], '-----------'),
|
||||
])
|
||||
@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')])
|
||||
def test_no_intersect(outer_widths, left, right, expected):
|
||||
"""Test with no column dividers.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths.
|
||||
:param str left: Left border.
|
||||
:param str right: Right border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
if left and right:
|
||||
expected = left + expected + right
|
||||
actual = build_border(outer_widths, '-', left, '', right, title='Applications')
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,expected', [
|
||||
([20], 'Applications--------'),
|
||||
([0, 20], 'Applications---------'),
|
||||
([20, 0], 'Applications--------+'),
|
||||
([0, 0, 20], 'Applications----------'),
|
||||
([20, 0, 0], 'Applications--------++'),
|
||||
|
||||
([10, 10], 'Applications---------'),
|
||||
([11, 9], 'Applications---------'),
|
||||
([12, 8], 'Applications+--------'),
|
||||
([13, 7], 'Applications-+-------'),
|
||||
|
||||
([5, 5, 5, 5], 'Applications-----+-----'),
|
||||
([4, 4, 6, 6], 'Applications----+------'),
|
||||
([3, 3, 7, 7], 'Applications---+-------'),
|
||||
([2, 2, 7, 9], 'Applications-+---------'),
|
||||
([1, 1, 9, 9], 'Applications-+---------'),
|
||||
|
||||
([2, 2, 2, 2, 2, 2, 2], 'Applications--+--+--'),
|
||||
([1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'Applications-+-+-+-'),
|
||||
([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'Applications++++++++'),
|
||||
|
||||
([2, 2, 2, 2], '--+--+--+--'),
|
||||
([1, 1, 1, 1, 1], '-+-+-+-+-'),
|
||||
([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], '+++++++++'),
|
||||
])
|
||||
@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')])
|
||||
def test_intersect(outer_widths, left, right, expected):
|
||||
"""Test with column dividers.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths.
|
||||
:param str left: Left border.
|
||||
:param str right: Right border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
if left and right:
|
||||
expected = left + expected + right
|
||||
actual = build_border(outer_widths, '-', left, '+', right, title='Applications')
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,intersect,expected', [
|
||||
([12], '+', u'蓝色--------'),
|
||||
([12], '', u'蓝色--------'),
|
||||
([7, 5], '+', u'蓝色---+-----'),
|
||||
([7, 5], '', u'蓝色--------'),
|
||||
([4], '+', u'蓝色'),
|
||||
([4], '', u'蓝色'),
|
||||
([4, 1], '+', u'蓝色+-'),
|
||||
([4, 1], '', u'蓝色-'),
|
||||
([4, 0], '+', u'蓝色+'),
|
||||
([4, 0], '', u'蓝色'),
|
||||
([12], '', u'蓝色--------'),
|
||||
([6, 6], '', u'蓝色--------'),
|
||||
([3, 3, 3, 3], '', u'蓝色--------'),
|
||||
([2, 1, 2, 1, 2, 1, 2, 1], '', u'蓝色--------'),
|
||||
([1] * 12, '', u'蓝色--------'),
|
||||
([2, 4], '', u'蓝色--'),
|
||||
([1, 4], '', u'蓝色-'),
|
||||
([1, 3], '', u'蓝色'),
|
||||
([1, 2], '', u'---'),
|
||||
([2], '', u'--'),
|
||||
([12], '+', u'蓝色--------'),
|
||||
([0, 12], '+', u'蓝色---------'),
|
||||
([12, 0], '+', u'蓝色--------+'),
|
||||
([0, 0, 12], '+', u'蓝色----------'),
|
||||
([12, 0, 0], '+', u'蓝色--------++'),
|
||||
([3, 3], '+', u'蓝色---'),
|
||||
([4, 2], '+', u'蓝色+--'),
|
||||
([5, 1], '+', u'蓝色-+-'),
|
||||
([3, 3, 3, 3], '+', u'蓝色---+---+---'),
|
||||
([2, 2, 4, 4], '+', u'蓝色-+----+----'),
|
||||
([1, 1, 5, 5], '+', u'蓝色-----+-----'),
|
||||
([2, 2, 2, 2], '+', u'蓝色-+--+--'),
|
||||
([1, 1, 1, 1, 1], '+', u'蓝色-+-+-'),
|
||||
([0, 0, 0, 0, 0, 0, 0], '+', u'蓝色++'),
|
||||
([1, 1], '+', u'-+-'),
|
||||
])
|
||||
@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')])
|
||||
def test_cjk(outer_widths, left, intersect, right, expected):
|
||||
"""Test with CJK characters in title.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths.
|
||||
:param str left: Left border.
|
||||
:param str intersect: Column separator.
|
||||
:param str right: Right border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
if left and right:
|
||||
expected = left + expected + right
|
||||
actual = build_border(outer_widths, '-', left, intersect, right, title=u'蓝色')
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,intersect,expected', [
|
||||
([12], '+', u'معرب--------'),
|
||||
([12], '', u'معرب--------'),
|
||||
([7, 5], '+', u'معرب---+-----'),
|
||||
([7, 5], '', u'معرب--------'),
|
||||
([4], '+', u'معرب'),
|
||||
([4], '', u'معرب'),
|
||||
([4, 1], '+', u'معرب+-'),
|
||||
([4, 1], '', u'معرب-'),
|
||||
([4, 0], '+', u'معرب+'),
|
||||
([4, 0], '', u'معرب'),
|
||||
([12], '', u'معرب--------'),
|
||||
([6, 6], '', u'معرب--------'),
|
||||
([3, 3, 3, 3], '', u'معرب--------'),
|
||||
([2, 1, 2, 1, 2, 1, 2, 1], '', u'معرب--------'),
|
||||
([1] * 12, '', u'معرب--------'),
|
||||
([2, 4], '', u'معرب--'),
|
||||
([1, 4], '', u'معرب-'),
|
||||
([1, 3], '', u'معرب'),
|
||||
([1, 2], '', u'---'),
|
||||
([2], '', u'--'),
|
||||
([12], '+', u'معرب--------'),
|
||||
([0, 12], '+', u'معرب---------'),
|
||||
([12, 0], '+', u'معرب--------+'),
|
||||
([0, 0, 12], '+', u'معرب----------'),
|
||||
([12, 0, 0], '+', u'معرب--------++'),
|
||||
([3, 3], '+', u'معرب---'),
|
||||
([4, 2], '+', u'معرب+--'),
|
||||
([5, 1], '+', u'معرب-+-'),
|
||||
([3, 3, 3, 3], '+', u'معرب---+---+---'),
|
||||
([2, 2, 4, 4], '+', u'معرب-+----+----'),
|
||||
([1, 1, 5, 5], '+', u'معرب-----+-----'),
|
||||
([2, 2, 2, 2], '+', u'معرب-+--+--'),
|
||||
([1, 1, 1, 1, 1], '+', u'معرب-+-+-'),
|
||||
([0, 0, 0, 0, 0, 0, 0], '+', u'معرب++'),
|
||||
([1, 1], '+', u'-+-'),
|
||||
])
|
||||
@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')])
|
||||
def test_rtl(outer_widths, left, intersect, right, expected):
|
||||
"""Test with RTL characters in title.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths.
|
||||
:param str left: Left border.
|
||||
:param str intersect: Column separator.
|
||||
:param str right: Right border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
if left and right:
|
||||
expected = left + expected + right
|
||||
actual = build_border(outer_widths, '-', left, intersect, right, title=u'معرب')
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,intersect,expected', [
|
||||
([12], '+', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([12], '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([7, 5], '+', '\x1b[34mTEST\x1b[0m---+-----'),
|
||||
([7, 5], '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([4], '+', '\x1b[34mTEST\x1b[0m'),
|
||||
([4], '', '\x1b[34mTEST\x1b[0m'),
|
||||
([4, 1], '+', '\x1b[34mTEST\x1b[0m+-'),
|
||||
([4, 1], '', '\x1b[34mTEST\x1b[0m-'),
|
||||
([4, 0], '+', '\x1b[34mTEST\x1b[0m+'),
|
||||
([4, 0], '', '\x1b[34mTEST\x1b[0m'),
|
||||
([12], '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([6, 6], '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([3, 3, 3, 3], '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([2, 1, 2, 1, 2, 1, 2, 1], '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([1] * 12, '', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([2, 4], '', '\x1b[34mTEST\x1b[0m--'),
|
||||
([1, 4], '', '\x1b[34mTEST\x1b[0m-'),
|
||||
([1, 3], '', '\x1b[34mTEST\x1b[0m'),
|
||||
([1, 2], '', '---'),
|
||||
([12], '+', '\x1b[34mTEST\x1b[0m--------'),
|
||||
([0, 12], '+', '\x1b[34mTEST\x1b[0m---------'),
|
||||
([12, 0], '+', '\x1b[34mTEST\x1b[0m--------+'),
|
||||
([0, 0, 12], '+', '\x1b[34mTEST\x1b[0m----------'),
|
||||
([12, 0, 0], '+', '\x1b[34mTEST\x1b[0m--------++'),
|
||||
([3, 3], '+', '\x1b[34mTEST\x1b[0m---'),
|
||||
([4, 2], '+', '\x1b[34mTEST\x1b[0m+--'),
|
||||
([5, 1], '+', '\x1b[34mTEST\x1b[0m-+-'),
|
||||
([3, 3, 3, 3], '+', '\x1b[34mTEST\x1b[0m---+---+---'),
|
||||
([2, 2, 4, 4], '+', '\x1b[34mTEST\x1b[0m-+----+----'),
|
||||
([1, 1, 5, 5], '+', '\x1b[34mTEST\x1b[0m-----+-----'),
|
||||
([2, 2, 2, 2], '+', '\x1b[34mTEST\x1b[0m-+--+--'),
|
||||
([1, 1, 1, 1, 1], '+', '\x1b[34mTEST\x1b[0m-+-+-'),
|
||||
([0, 0, 0, 0, 0, 0, 0], '+', '\x1b[34mTEST\x1b[0m++'),
|
||||
([1, 1], '+', '-+-'),
|
||||
])
|
||||
@pytest.mark.parametrize('left,right', [('', ''), ('<', '>')])
|
||||
@pytest.mark.parametrize('title', [
|
||||
'\x1b[34mTEST\x1b[0m',
|
||||
Color('{blue}TEST{/all}'),
|
||||
Fore.BLUE + 'TEST' + Style.RESET_ALL,
|
||||
colored('TEST', 'blue'),
|
||||
])
|
||||
def test_colors(outer_widths, left, intersect, right, title, expected):
|
||||
"""Test with color title characters.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths with padding.
|
||||
:param str left: Left border.
|
||||
:param str intersect: Column separator.
|
||||
:param str right: Right border.
|
||||
:param title: Title in border with color codes.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
if left and right:
|
||||
expected = left + expected + right
|
||||
actual = build_border(outer_widths, '-', left, intersect, right, title=title)
|
||||
assert ''.join(actual) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('outer_widths,title,expected', [
|
||||
([3, 3, 3], 123, '<123+---+--->'),
|
||||
([3, 3, 3], 0.9, '<0.9+---+--->'),
|
||||
([3, 3, 3], True, '<True---+--->'),
|
||||
([3, 3, 3], False, '<False--+--->'),
|
||||
])
|
||||
def test_non_string(outer_widths, title, expected):
|
||||
"""Test with non-string values.
|
||||
|
||||
:param iter outer_widths: List of integers representing column widths with padding.
|
||||
:param title: Title in border.
|
||||
:param str expected: Expected output.
|
||||
"""
|
||||
actual = build_border(outer_widths, '-', '<', '+', '>', title=title)
|
||||
assert ''.join(actual) == expected
|
104
tests/test_build/test_build_row.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
"""Test function in module."""
|
||||
|
||||
from terminaltables.build import build_row
|
||||
|
||||
|
||||
def test_one_line():
|
||||
"""Test with one line cells."""
|
||||
row = [
|
||||
['Left Cell'], ['Center Cell'], ['Right Cell'],
|
||||
]
|
||||
actual = [tuple(i) for i in build_row(row, '>', '|', '<')]
|
||||
expected = [
|
||||
('>', 'Left Cell', '|', 'Center Cell', '|', 'Right Cell', '<'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_two_line():
|
||||
"""Test with two line cells."""
|
||||
row = [
|
||||
[
|
||||
'Left ',
|
||||
'Cell1',
|
||||
],
|
||||
|
||||
[
|
||||
'Center',
|
||||
'Cell2 ',
|
||||
],
|
||||
|
||||
[
|
||||
'Right',
|
||||
'Cell3',
|
||||
],
|
||||
]
|
||||
actual = [tuple(i) for i in build_row(row, '>', '|', '<')]
|
||||
expected = [
|
||||
('>', 'Left ', '|', 'Center', '|', 'Right', '<'),
|
||||
('>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_three_line():
|
||||
"""Test with three line cells."""
|
||||
row = [
|
||||
[
|
||||
'Left ',
|
||||
'Cell1',
|
||||
' ',
|
||||
],
|
||||
|
||||
[
|
||||
'Center',
|
||||
'Cell2 ',
|
||||
' ',
|
||||
],
|
||||
|
||||
[
|
||||
'Right',
|
||||
'Cell3',
|
||||
' ',
|
||||
],
|
||||
]
|
||||
actual = [tuple(i) for i in build_row(row, '>', '|', '<')]
|
||||
expected = [
|
||||
('>', 'Left ', '|', 'Center', '|', 'Right', '<'),
|
||||
('>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'),
|
||||
('>', ' ', '|', ' ', '|', ' ', '<'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_single():
|
||||
"""Test with single cell."""
|
||||
actual = [tuple(i) for i in build_row([['Cell']], '>', '|', '<')]
|
||||
expected = [
|
||||
('>', 'Cell', '<'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_empty():
|
||||
"""Test with empty cell."""
|
||||
actual = [tuple(i) for i in build_row([['']], '>', '|', '<')]
|
||||
expected = [
|
||||
('>', '', '<'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_no_cells():
|
||||
"""Test with no cells."""
|
||||
actual = [tuple(i) for i in build_row([[]], '>', '|', '<')]
|
||||
expected = [
|
||||
('>', '<'),
|
||||
]
|
||||
assert actual == expected
|
||||
|
||||
actual = [tuple(i) for i in build_row([], '>', '|', '<')]
|
||||
expected = [
|
||||
('>', '<'),
|
||||
]
|
||||
assert actual == expected
|
37
tests/test_build/test_combine.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.build import combine
|
||||
|
||||
|
||||
@pytest.mark.parametrize('generator', [False, True])
|
||||
def test_borders(generator):
|
||||
"""Test with borders.
|
||||
|
||||
:param bool generator: Test with generator instead of list.
|
||||
"""
|
||||
line = ['One', 'Two', 'Three']
|
||||
actual = list(combine(iter(line) if generator else line, '>', '|', '<'))
|
||||
assert actual == ['>', 'One', '|', 'Two', '|', 'Three', '<']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('generator', [False, True])
|
||||
def test_no_border(generator):
|
||||
"""Test without borders.
|
||||
|
||||
:param bool generator: Test with generator instead of list.
|
||||
"""
|
||||
line = ['One', 'Two', 'Three']
|
||||
actual = list(combine(iter(line) if generator else line, '', '', ''))
|
||||
assert actual == ['One', 'Two', 'Three']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('generator', [False, True])
|
||||
def test_no_items(generator):
|
||||
"""Test with empty list.
|
||||
|
||||
:param bool generator: Test with generator instead of list.
|
||||
"""
|
||||
actual = list(combine(iter([]) if generator else [], '>', '|', '<'))
|
||||
assert actual == ['>', '<']
|
25
tests/test_build/test_flatten.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""Test function in module."""
|
||||
|
||||
from terminaltables.build import flatten
|
||||
|
||||
|
||||
def test_one_line():
|
||||
"""Test with one line cells."""
|
||||
table = [
|
||||
['>', 'Left Cell', '|', 'Center Cell', '|', 'Right Cell', '<'],
|
||||
]
|
||||
actual = flatten(table)
|
||||
expected = '>Left Cell|Center Cell|Right Cell<'
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_two_line():
|
||||
"""Test with two line cells."""
|
||||
table = [
|
||||
['>', 'Left ', '|', 'Center', '|', 'Right', '<'],
|
||||
['>', 'Cell1', '|', 'Cell2 ', '|', 'Cell3', '<'],
|
||||
]
|
||||
actual = flatten(table)
|
||||
expected = ('>Left |Center|Right<\n'
|
||||
'>Cell1|Cell2 |Cell3<')
|
||||
assert actual == expected
|
32
tests/test_examples.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
"""Test example scripts."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from tests import PROJECT_ROOT
|
||||
|
||||
|
||||
@pytest.mark.parametrize('filename', map('example{0}.py'.format, (1, 2, 3)))
|
||||
def test(filename):
|
||||
"""Test with subprocess.
|
||||
|
||||
:param str filename: Example script filename to run.
|
||||
"""
|
||||
command = [sys.executable, str(PROJECT_ROOT.join(filename))]
|
||||
env = dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
|
||||
# Run.
|
||||
proc = subprocess.Popen(command, env=env, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
|
||||
output = proc.communicate()[0]
|
||||
|
||||
# Verify.
|
||||
try:
|
||||
assert proc.poll() == 0
|
||||
except AssertionError:
|
||||
print(output)
|
||||
raise
|
45
tests/test_terminal_io/__init__.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
"""Common objects used by tests in directory."""
|
||||
|
||||
from terminaltables import terminal_io
|
||||
|
||||
|
||||
class MockKernel32(object):
|
||||
"""Mock kernel32."""
|
||||
|
||||
def __init__(self, stderr=terminal_io.INVALID_HANDLE_VALUE, stdout=terminal_io.INVALID_HANDLE_VALUE):
|
||||
"""Constructor."""
|
||||
self.stderr = stderr
|
||||
self.stdout = stdout
|
||||
self.csbi_err = b'x\x00)#\x00\x00\x87\x05\x07\x00\x00\x00j\x05w\x00\x87\x05x\x00J\x00' # 119 x 29
|
||||
self.csbi_out = b'L\x00,\x01\x00\x00*\x01\x07\x00\x00\x00\x0e\x01K\x00*\x01L\x00L\x00' # 75 x 28
|
||||
self.setConsoleTitleA_called = False
|
||||
self.setConsoleTitleW_called = False
|
||||
|
||||
def GetConsoleScreenBufferInfo(self, handle, lpcsbi): # noqa
|
||||
"""Mock GetConsoleScreenBufferInfo.
|
||||
|
||||
:param handle: Unused handle.
|
||||
:param lpcsbi: ctypes.create_string_buffer() return value.
|
||||
"""
|
||||
if handle == self.stderr:
|
||||
lpcsbi.raw = self.csbi_err
|
||||
else:
|
||||
lpcsbi.raw = self.csbi_out
|
||||
return 1
|
||||
|
||||
def GetStdHandle(self, handle): # noqa
|
||||
"""Mock GetStdHandle.
|
||||
|
||||
:param int handle: STD_ERROR_HANDLE or STD_OUTPUT_HANDLE.
|
||||
"""
|
||||
return self.stderr if handle == terminal_io.STD_ERROR_HANDLE else self.stdout
|
||||
|
||||
def SetConsoleTitleA(self, _): # noqa
|
||||
"""Mock SetConsoleTitleA."""
|
||||
self.setConsoleTitleA_called = True
|
||||
return 1
|
||||
|
||||
def SetConsoleTitleW(self, _): # noqa
|
||||
"""Mock SetConsoleTitleW."""
|
||||
self.setConsoleTitleW_called = True
|
||||
return 1
|
BIN
tests/test_terminal_io/sub_title_ascii_win10.bmp
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
tests/test_terminal_io/sub_title_ascii_win2012.bmp
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
tests/test_terminal_io/sub_title_ascii_winxp.bmp
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
tests/test_terminal_io/sub_title_cjk_win10.bmp
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
tests/test_terminal_io/sub_title_cjk_win2012.bmp
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
tests/test_terminal_io/sub_title_cjk_winxp.bmp
Normal file
After Width: | Height: | Size: 4.2 KiB |
28
tests/test_terminal_io/test_get_console_info.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import ctypes
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.terminal_io import get_console_info, INVALID_HANDLE_VALUE, IS_WINDOWS
|
||||
|
||||
from tests.test_terminal_io import MockKernel32
|
||||
|
||||
|
||||
def test():
|
||||
"""Test function."""
|
||||
# Test live WinError.
|
||||
if IS_WINDOWS:
|
||||
with pytest.raises(OSError):
|
||||
get_console_info(ctypes.windll.kernel32, 0)
|
||||
|
||||
# Test INVALID_HANDLE_VALUE.
|
||||
kernel32 = MockKernel32(stderr=1)
|
||||
with pytest.raises(OSError):
|
||||
get_console_info(kernel32, INVALID_HANDLE_VALUE)
|
||||
|
||||
# Test no error with mock methods.
|
||||
width, height = get_console_info(kernel32, 1)
|
||||
assert width == 119
|
||||
assert height == 29
|
110
tests/test_terminal_io/test_set_terminal_title.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import py
|
||||
import pytest
|
||||
|
||||
from terminaltables.terminal_io import IS_WINDOWS, set_terminal_title
|
||||
|
||||
from tests import PROJECT_ROOT
|
||||
from tests.screenshot import RunNewConsole, screenshot_until_match
|
||||
from tests.test_terminal_io import MockKernel32
|
||||
|
||||
HERE = py.path.local(__file__).dirpath()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_windows', [False, True])
|
||||
@pytest.mark.parametrize('mode', ['ascii', 'unicode', 'bytes'])
|
||||
def test(monkeypatch, is_windows, mode):
|
||||
"""Test function.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
:param bool is_windows: Monkeypatch terminal_io.IS_WINDOWS
|
||||
:param str mode: Scenario to test for.
|
||||
"""
|
||||
monkeypatch.setattr('terminaltables.terminal_io.IS_WINDOWS', is_windows)
|
||||
kernel32 = MockKernel32()
|
||||
|
||||
# Title.
|
||||
if mode == 'ascii':
|
||||
title = 'Testing terminaltables.'
|
||||
elif mode == 'unicode':
|
||||
title = u'Testing terminaltables with unicode: 世界你好蓝色'
|
||||
else:
|
||||
title = b'Testing terminaltables with bytes.'
|
||||
|
||||
# Run.
|
||||
assert set_terminal_title(title, kernel32)
|
||||
if not is_windows:
|
||||
return
|
||||
|
||||
# Verify.
|
||||
if mode == 'ascii':
|
||||
assert kernel32.setConsoleTitleA_called
|
||||
assert not kernel32.setConsoleTitleW_called
|
||||
elif mode == 'unicode':
|
||||
assert not kernel32.setConsoleTitleA_called
|
||||
assert kernel32.setConsoleTitleW_called
|
||||
else:
|
||||
assert kernel32.setConsoleTitleA_called
|
||||
assert not kernel32.setConsoleTitleW_called
|
||||
|
||||
|
||||
@pytest.mark.skipif(str(not IS_WINDOWS))
|
||||
@pytest.mark.skipif('True') # https://github.com/Robpol86/terminaltables/issues/30
|
||||
@pytest.mark.parametrize('mode', ['ascii', 'unicode', 'bytes'])
|
||||
def test_windows_screenshot(tmpdir, mode):
|
||||
"""Test function on Windows in a new console window. Take a screenshot to verify it works.
|
||||
|
||||
:param tmpdir: pytest fixture.
|
||||
:param str mode: Scenario to test for.
|
||||
"""
|
||||
script = tmpdir.join('script.py')
|
||||
command = [sys.executable, str(script)]
|
||||
change_title = tmpdir.join('change_title')
|
||||
screenshot = PROJECT_ROOT.join('test_terminal_io.png')
|
||||
if screenshot.check():
|
||||
screenshot.remove()
|
||||
|
||||
# Determine title.
|
||||
if mode == 'ascii':
|
||||
title = "'test ASCII test'"
|
||||
elif mode == 'unicode':
|
||||
title = u"u'test 世界你好蓝色 test'"
|
||||
else:
|
||||
title = "b'test ASCII test'"
|
||||
|
||||
# Generate script.
|
||||
script_template = dedent(u"""\
|
||||
# coding: utf-8
|
||||
from __future__ import print_function
|
||||
import os, time
|
||||
from terminaltables.terminal_io import set_terminal_title
|
||||
stop_after = time.time() + 20
|
||||
|
||||
print('Waiting for FindWindowA() in RunNewConsole.__enter__()...')
|
||||
while not os.path.exists(r'{change_title}') and time.time() < stop_after:
|
||||
time.sleep(0.5)
|
||||
assert set_terminal_title({title}) is True
|
||||
|
||||
print('Waiting for screenshot_until_match()...')
|
||||
while not os.path.exists(r'{screenshot}') and time.time() < stop_after:
|
||||
time.sleep(0.5)
|
||||
""")
|
||||
script_contents = script_template.format(change_title=str(change_title), title=title, screenshot=str(screenshot))
|
||||
script.write(script_contents.encode('utf-8'), mode='wb')
|
||||
|
||||
# Setup expected.
|
||||
if mode == 'unicode':
|
||||
sub_images = [str(p) for p in HERE.listdir('sub_title_cjk_*.bmp')]
|
||||
else:
|
||||
sub_images = [str(p) for p in HERE.listdir('sub_title_ascii_*.bmp')]
|
||||
assert sub_images
|
||||
|
||||
# Run.
|
||||
with RunNewConsole(command) as gen:
|
||||
change_title.ensure(file=True) # Touch file.
|
||||
screenshot_until_match(str(screenshot), 15, sub_images, 1, gen)
|
54
tests/test_terminal_io/test_terminal_size.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.terminal_io import DEFAULT_HEIGHT, DEFAULT_WIDTH, INVALID_HANDLE_VALUE, IS_WINDOWS, terminal_size
|
||||
|
||||
from tests.test_terminal_io import MockKernel32
|
||||
|
||||
|
||||
@pytest.mark.parametrize('stderr', [1, INVALID_HANDLE_VALUE])
|
||||
@pytest.mark.parametrize('stdout', [2, INVALID_HANDLE_VALUE])
|
||||
def test_windows(monkeypatch, stderr, stdout):
|
||||
"""Test function with IS_WINDOWS=True.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
:param int stderr: Mock handle value.
|
||||
:param int stdout: Mock handle value.
|
||||
"""
|
||||
monkeypatch.setattr('terminaltables.terminal_io.IS_WINDOWS', True)
|
||||
|
||||
kernel32 = MockKernel32(stderr=stderr, stdout=stdout)
|
||||
width, height = terminal_size(kernel32)
|
||||
|
||||
if stderr == INVALID_HANDLE_VALUE and stdout == INVALID_HANDLE_VALUE:
|
||||
assert width == DEFAULT_WIDTH
|
||||
assert height == DEFAULT_HEIGHT
|
||||
elif stdout == INVALID_HANDLE_VALUE:
|
||||
assert width == 119
|
||||
assert height == 29
|
||||
elif stderr == INVALID_HANDLE_VALUE:
|
||||
assert width == 75
|
||||
assert height == 28
|
||||
else:
|
||||
assert width == 119
|
||||
assert height == 29
|
||||
|
||||
|
||||
@pytest.mark.skipif(str(IS_WINDOWS))
|
||||
def test_nix(monkeypatch):
|
||||
"""Test function with IS_WINDOWS=False.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
"""
|
||||
# Test exception (no terminal within pytest).
|
||||
width, height = terminal_size()
|
||||
assert width == DEFAULT_WIDTH
|
||||
assert height == DEFAULT_HEIGHT
|
||||
|
||||
# Test mocked.
|
||||
monkeypatch.setattr('fcntl.ioctl', lambda *_: b'\x1d\x00w\x00\xca\x02\x96\x01')
|
||||
width, height = terminal_size()
|
||||
assert width == 119
|
||||
assert height == 29
|
202
tests/test_width_and_alignment/test_align_and_pad_cell.py
Normal file
|
@ -0,0 +1,202 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
from colorama import Fore
|
||||
from colorclass import Color
|
||||
from termcolor import colored
|
||||
|
||||
from terminaltables.width_and_alignment import align_and_pad_cell
|
||||
|
||||
|
||||
@pytest.mark.parametrize('string,align,width,expected', [
|
||||
('test', '', 4, ['test']),
|
||||
(123, '', 3, ['123']),
|
||||
(0.9, '', 3, ['0.9']),
|
||||
(None, '', 4, ['None']),
|
||||
(True, '', 4, ['True']),
|
||||
(False, '', 5, ['False']),
|
||||
(Color('{blue}Test{/blue}'), '', 4, ['\x1b[34mTest\x1b[39m']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, '', 4, ['\x1b[34mTest\x1b[39m']),
|
||||
(colored('Test', 'blue'), '', 4, ['\x1b[34mTest\x1b[0m']),
|
||||
('蓝色', '', 4, ['蓝色']),
|
||||
(u'שלום', '', 4, [u'\u05e9\u05dc\u05d5\u05dd']),
|
||||
(u'معرب', '', 4, [u'\u0645\u0639\u0631\u0628']),
|
||||
|
||||
('test', '', 5, ['test ']),
|
||||
(123, '', 4, ['123 ']),
|
||||
(0.9, '', 4, ['0.9 ']),
|
||||
(None, '', 5, ['None ']),
|
||||
(True, '', 5, ['True ']),
|
||||
(False, '', 6, ['False ']),
|
||||
(Color('{blue}Test{/blue}'), '', 5, ['\x1b[34mTest\x1b[39m ']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, '', 5, ['\x1b[34mTest\x1b[39m ']),
|
||||
(colored('Test', 'blue'), '', 5, ['\x1b[34mTest\x1b[0m ']),
|
||||
('蓝色', '', 5, ['蓝色 ']),
|
||||
(u'שלום', '', 5, [u'\u05e9\u05dc\u05d5\u05dd ']),
|
||||
(u'معرب', '', 5, [u'\u0645\u0639\u0631\u0628 ']),
|
||||
|
||||
('test', 'left', 5, ['test ']),
|
||||
(123, 'left', 4, ['123 ']),
|
||||
(0.9, 'left', 4, ['0.9 ']),
|
||||
(None, 'left', 5, ['None ']),
|
||||
(True, 'left', 5, ['True ']),
|
||||
(False, 'left', 6, ['False ']),
|
||||
(Color('{blue}Test{/blue}'), 'left', 5, ['\x1b[34mTest\x1b[39m ']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, 'left', 5, ['\x1b[34mTest\x1b[39m ']),
|
||||
(colored('Test', 'blue'), 'left', 5, ['\x1b[34mTest\x1b[0m ']),
|
||||
('蓝色', 'left', 5, ['蓝色 ']),
|
||||
(u'שלום', 'left', 5, [u'\u05e9\u05dc\u05d5\u05dd ']),
|
||||
(u'معرب', 'left', 5, [u'\u0645\u0639\u0631\u0628 ']),
|
||||
|
||||
('test', 'right', 5, [' test']),
|
||||
(123, 'right', 4, [' 123']),
|
||||
(0.9, 'right', 4, [' 0.9']),
|
||||
(None, 'right', 5, [' None']),
|
||||
(True, 'right', 5, [' True']),
|
||||
(False, 'right', 6, [' False']),
|
||||
(Color('{blue}Test{/blue}'), 'right', 5, [' \x1b[34mTest\x1b[39m']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, 'right', 5, [' \x1b[34mTest\x1b[39m']),
|
||||
(colored('Test', 'blue'), 'right', 5, [' \x1b[34mTest\x1b[0m']),
|
||||
('蓝色', 'right', 5, [' 蓝色']),
|
||||
(u'שלום', 'right', 5, [u' \u05e9\u05dc\u05d5\u05dd']),
|
||||
(u'معرب', 'right', 5, [u' \u0645\u0639\u0631\u0628']),
|
||||
|
||||
('test', 'center', 6, [' test ']),
|
||||
(123, 'center', 5, [' 123 ']),
|
||||
(0.9, 'center', 5, [' 0.9 ']),
|
||||
(None, 'center', 6, [' None ']),
|
||||
(True, 'center', 6, [' True ']),
|
||||
(False, 'center', 7, [' False ']),
|
||||
(Color('{blue}Test{/blue}'), 'center', 6, [' \x1b[34mTest\x1b[39m ']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, 'center', 6, [' \x1b[34mTest\x1b[39m ']),
|
||||
(colored('Test', 'blue'), 'center', 6, [' \x1b[34mTest\x1b[0m ']),
|
||||
('蓝色', 'center', 6, [' 蓝色 ']),
|
||||
(u'שלום', 'center', 6, [u' \u05e9\u05dc\u05d5\u05dd ']),
|
||||
(u'معرب', 'center', 6, [u' \u0645\u0639\u0631\u0628 ']),
|
||||
])
|
||||
def test_width(string, align, width, expected):
|
||||
"""Test width and horizontal alignment.
|
||||
|
||||
:param str string: String to test.
|
||||
:param str align: Horizontal alignment.
|
||||
:param int width: Expand string to this width without padding.
|
||||
:param list expected: Expected output string.
|
||||
"""
|
||||
actual = align_and_pad_cell(string, (align,), (width, 1), (0, 0, 0, 0))
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('string,align,height,expected', [
|
||||
('test', '', 1, ['test']),
|
||||
(Color('{blue}Test{/blue}'), '', 1, ['\x1b[34mTest\x1b[39m']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, '', 1, ['\x1b[34mTest\x1b[39m']),
|
||||
(colored('Test', 'blue'), '', 1, ['\x1b[34mTest\x1b[0m']),
|
||||
('蓝色', '', 1, ['蓝色']),
|
||||
(u'שלום', '', 1, [u'\u05e9\u05dc\u05d5\u05dd']),
|
||||
(u'معرب', '', 1, [u'\u0645\u0639\u0631\u0628']),
|
||||
|
||||
('test', '', 2, ['test', ' ']),
|
||||
(Color('{blue}Test{/blue}'), '', 2, ['\x1b[34mTest\x1b[39m', ' ']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, '', 2, ['\x1b[34mTest\x1b[39m', ' ']),
|
||||
(colored('Test', 'blue'), '', 2, ['\x1b[34mTest\x1b[0m', ' ']),
|
||||
('蓝色', '', 2, ['蓝色', ' ']),
|
||||
(u'שלום', '', 2, [u'\u05e9\u05dc\u05d5\u05dd', ' ']),
|
||||
(u'معرب', '', 2, [u'\u0645\u0639\u0631\u0628', ' ']),
|
||||
|
||||
('test', 'top', 2, ['test', ' ']),
|
||||
(Color('{blue}Test{/blue}'), 'top', 2, ['\x1b[34mTest\x1b[39m', ' ']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, 'top', 2, ['\x1b[34mTest\x1b[39m', ' ']),
|
||||
(colored('Test', 'blue'), 'top', 2, ['\x1b[34mTest\x1b[0m', ' ']),
|
||||
('蓝色', 'top', 2, ['蓝色', ' ']),
|
||||
(u'שלום', 'top', 2, [u'\u05e9\u05dc\u05d5\u05dd', ' ']),
|
||||
(u'معرب', 'top', 2, [u'\u0645\u0639\u0631\u0628', ' ']),
|
||||
|
||||
('test', 'bottom', 2, [' ', 'test']),
|
||||
(Color('{blue}Test{/blue}'), 'bottom', 2, [' ', '\x1b[34mTest\x1b[39m']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, 'bottom', 2, [' ', '\x1b[34mTest\x1b[39m']),
|
||||
(colored('Test', 'blue'), 'bottom', 2, [' ', '\x1b[34mTest\x1b[0m']),
|
||||
('蓝色', 'bottom', 2, [' ', '蓝色']),
|
||||
(u'שלום', 'bottom', 2, [' ', u'\u05e9\u05dc\u05d5\u05dd']),
|
||||
(u'معرب', 'bottom', 2, [' ', u'\u0645\u0639\u0631\u0628']),
|
||||
|
||||
('test', 'middle', 3, [' ', 'test', ' ']),
|
||||
(Color('{blue}Test{/blue}'), 'middle', 3, [' ', '\x1b[34mTest\x1b[39m', ' ']),
|
||||
(Fore.BLUE + 'Test' + Fore.RESET, 'middle', 3, [' ', '\x1b[34mTest\x1b[39m', ' ']),
|
||||
(colored('Test', 'blue'), 'middle', 3, [' ', '\x1b[34mTest\x1b[0m', ' ']),
|
||||
('蓝色', 'middle', 3, [' ', '蓝色', ' ']),
|
||||
(u'שלום', 'middle', 3, [' ', u'\u05e9\u05dc\u05d5\u05dd', ' ']),
|
||||
(u'معرب', 'middle', 3, [' ', u'\u0645\u0639\u0631\u0628', ' ']),
|
||||
])
|
||||
def test_height(string, align, height, expected):
|
||||
"""Test height and vertical alignment.
|
||||
|
||||
:param str string: String to test.
|
||||
:param str align: Horizontal alignment.
|
||||
:param int height: Expand string to this height without padding.
|
||||
:param list expected: Expected output string.
|
||||
"""
|
||||
actual = align_and_pad_cell(string, (align,), (4, height), (0, 0, 0, 0))
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('string,align,expected', [
|
||||
('', '', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', '', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', '', ['.......', '.a.....', '.b.....', '.c.....', '.......']),
|
||||
('test', '', ['.......', '.test..', '.......', '.......', '.......']),
|
||||
|
||||
('', 'left', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', 'left', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', 'left', ['.......', '.a.....', '.b.....', '.c.....', '.......']),
|
||||
('test', 'left', ['.......', '.test..', '.......', '.......', '.......']),
|
||||
|
||||
('', 'right', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', 'right', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', 'right', ['.......', '.....a.', '.....b.', '.....c.', '.......']),
|
||||
('test', 'right', ['.......', '..test.', '.......', '.......', '.......']),
|
||||
|
||||
('', 'center', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', 'center', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', 'center', ['.......', '...a...', '...b...', '...c...', '.......']),
|
||||
('test', 'center', ['.......', '..test.', '.......', '.......', '.......']),
|
||||
|
||||
('', 'top', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', 'top', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', 'top', ['.......', '.a.....', '.b.....', '.c.....', '.......']),
|
||||
('test', 'top', ['.......', '.test..', '.......', '.......', '.......']),
|
||||
|
||||
('', 'bottom', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', 'bottom', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', 'bottom', ['.......', '.a.....', '.b.....', '.c.....', '.......']),
|
||||
('test', 'bottom', ['.......', '.......', '.......', '.test..', '.......']),
|
||||
|
||||
('', 'middle', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('\n', 'middle', ['.......', '.......', '.......', '.......', '.......']),
|
||||
('a\nb\nc', 'middle', ['.......', '.a.....', '.b.....', '.c.....', '.......']),
|
||||
('test', 'middle', ['.......', '.......', '.test..', '.......', '.......']),
|
||||
|
||||
(
|
||||
u'蓝色\nשלום\nمعرب',
|
||||
'',
|
||||
['.......', u'.蓝色..', u'.\u05e9\u05dc\u05d5\u05dd..', u'.\u0645\u0639\u0631\u0628..', '.......']
|
||||
),
|
||||
|
||||
(
|
||||
'\n'.join((Color('{blue}Test{/blue}'), Fore.BLUE + 'Test' + Fore.RESET, colored('Test', 'blue'))),
|
||||
'',
|
||||
['.......', '.\x1b[34mTest\x1b[39m..', '.\x1b[34mTest\x1b[39m..', '.\x1b[34mTest\x1b[0m..', '.......']
|
||||
),
|
||||
|
||||
# (Color('{blue}A\nB{/blue}'), '', '.......\n.\x1b[34mA\x1b[39m.....\n.\x1b[34mB\x1b[39m.....\n.......\n.......'),
|
||||
|
||||
])
|
||||
def test_odd_width_height_pad_space(string, align, expected):
|
||||
"""Test odd number width, height, padding, and dots for whitespaces.
|
||||
|
||||
:param str string: String to test.
|
||||
:param str align: Alignment in any dimension but one at a time.
|
||||
:param list expected: Expected output string.
|
||||
"""
|
||||
actual = align_and_pad_cell(string, (align,), (5, 3), (1, 1, 1, 1), '.')
|
||||
assert actual == expected
|
107
tests/test_width_and_alignment/test_column_max_width.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from terminaltables.width_and_alignment import column_max_width, max_dimensions
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch(monkeypatch):
|
||||
"""Monkeypatch before every test function in this module.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
"""
|
||||
monkeypatch.setattr('terminaltables.width_and_alignment.terminal_size', lambda: (79, 24))
|
||||
|
||||
|
||||
def test_empty():
|
||||
"""Test with zero-length cells."""
|
||||
assert column_max_width(max_dimensions([['']])[0], 0, 0, 0, 0) == 79
|
||||
assert column_max_width(max_dimensions([['', '', '']])[0], 0, 0, 0, 0) == 79
|
||||
assert column_max_width(max_dimensions([['', '', ''], ['', '', '']])[0], 0, 0, 0, 0) == 79
|
||||
|
||||
assert column_max_width(max_dimensions([['']])[0], 0, 2, 1, 2) == 75
|
||||
assert column_max_width(max_dimensions([['', '', '']])[0], 0, 2, 1, 2) == 69
|
||||
assert column_max_width(max_dimensions([['', '', ''], ['', '', '']])[0], 0, 2, 1, 2) == 69
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test with single-line cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
inner_widths = max_dimensions(table_data)[0]
|
||||
|
||||
# '| Lettuce | green | vegetable |'
|
||||
outer, inner, padding = 2, 1, 2
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 55
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 53
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 57
|
||||
|
||||
# ' Lettuce | green | vegetable '
|
||||
outer = 0
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 57
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 55
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 59
|
||||
|
||||
# '| Lettuce green vegetable |'
|
||||
outer, inner = 2, 0
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 57
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 55
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 59
|
||||
|
||||
# ' Lettuce green vegetable '
|
||||
outer = 0
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 59
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 57
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 61
|
||||
|
||||
# '|Lettuce |green |vegetable |'
|
||||
outer, inner, padding = 2, 1, 1
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 58
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 56
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 60
|
||||
|
||||
# '|Lettuce |green |vegetable |'
|
||||
padding = 5
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 46
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 44
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 48
|
||||
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green', 'fruit'],
|
||||
]
|
||||
inner_widths = max_dimensions(table_data)[0]
|
||||
outer, inner, padding = 2, 1, 2
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 55
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 50
|
||||
assert column_max_width(inner_widths, 2, outer, inner, padding) == 54
|
||||
|
||||
|
||||
def test_multi_line(monkeypatch):
|
||||
"""Test with multi-line cells.
|
||||
|
||||
:param monkeypatch: pytest fixture.
|
||||
"""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', ('Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\n'
|
||||
'Susie Carmichael, Dil Pickles, Kimi Finster, Spike')],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
inner_widths = max_dimensions(table_data)[0]
|
||||
outer, inner, padding = 2, 1, 2
|
||||
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == -11
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 62
|
||||
|
||||
monkeypatch.setattr('terminaltables.width_and_alignment.terminal_size', lambda: (100, 24))
|
||||
assert column_max_width(inner_widths, 0, outer, inner, padding) == 10
|
||||
assert column_max_width(inner_widths, 1, outer, inner, padding) == 83
|
100
tests/test_width_and_alignment/test_max_dimensions.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
from colorama import Fore
|
||||
from colorclass import Color
|
||||
from termcolor import colored
|
||||
|
||||
from terminaltables.width_and_alignment import max_dimensions
|
||||
|
||||
|
||||
@pytest.mark.parametrize('table_data,expected_w,expected_h', [
|
||||
([[]], [], [0]),
|
||||
([['']], [0], [0]),
|
||||
([['', '']], [0, 0], [0]),
|
||||
|
||||
([[], []], [], [0, 0]),
|
||||
([[''], ['']], [0], [0, 0]),
|
||||
([['', ''], ['', '']], [0, 0], [0, 0]),
|
||||
])
|
||||
def test_zero_length(table_data, expected_w, expected_h):
|
||||
"""Test zero-length or empty tables.
|
||||
|
||||
:param list table_data: Input table data to test.
|
||||
:param list expected_w: Expected widths.
|
||||
:param list expected_h: Expected heights.
|
||||
"""
|
||||
actual = max_dimensions(table_data)
|
||||
assert actual == (expected_w, expected_h, expected_w, expected_h)
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test widths."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
assert max_dimensions(table_data, 1, 1) == ([7, 5, 9], [1, 1, 1, 1], [9, 7, 11], [1, 1, 1, 1])
|
||||
|
||||
table_data.append(['Watermelon', 'green', 'fruit'])
|
||||
assert max_dimensions(table_data, 2, 2) == ([10, 5, 9], [1, 1, 1, 1, 1], [14, 9, 13], [1, 1, 1, 1, 1])
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test heights."""
|
||||
table_data = [
|
||||
['One\nTwo', 'Buckle\nMy\nShoe'],
|
||||
]
|
||||
assert max_dimensions(table_data, 0, 0, 1, 1) == ([3, 6], [3], [3, 6], [5])
|
||||
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', ('Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\n'
|
||||
'Susie Carmichael, Dil Pickles, Kimi Finster, Spike')],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
assert max_dimensions(table_data, 0, 0, 2, 2) == ([10, 83], [1, 2, 1], [10, 83], [5, 6, 5])
|
||||
|
||||
|
||||
def test_trailing_newline():
|
||||
r"""Test with trailing \n."""
|
||||
table_data = [
|
||||
['Row One\n<blank>'],
|
||||
['<blank>\nRow Two'],
|
||||
['Row Three\n'],
|
||||
['\nRow Four'],
|
||||
]
|
||||
assert max_dimensions(table_data) == ([9], [2, 2, 2, 2], [9], [2, 2, 2, 2])
|
||||
|
||||
|
||||
def test_colors_cjk_rtl():
|
||||
"""Test color text, CJK characters, and RTL characters."""
|
||||
table_data = [
|
||||
[Color('{blue}Test{/blue}')],
|
||||
[Fore.BLUE + 'Test' + Fore.RESET],
|
||||
[colored('Test', 'blue')],
|
||||
]
|
||||
assert max_dimensions(table_data) == ([4], [1, 1, 1], [4], [1, 1, 1])
|
||||
|
||||
table_data = [
|
||||
['蓝色'],
|
||||
['世界你好'],
|
||||
]
|
||||
assert max_dimensions(table_data) == ([8], [1, 1], [8], [1, 1])
|
||||
|
||||
table_data = [
|
||||
['שלום'],
|
||||
['معرب'],
|
||||
]
|
||||
assert max_dimensions(table_data) == ([4], [1, 1], [4], [1, 1])
|
||||
|
||||
|
||||
def test_non_string():
|
||||
"""Test with non-string values."""
|
||||
table_data = [
|
||||
[123, 0.9, None, True, False],
|
||||
]
|
||||
assert max_dimensions(table_data) == ([3, 3, 4, 4, 5], [1], [3, 3, 4, 4, 5], [1])
|
70
tests/test_width_and_alignment/test_table_width.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
"""Test function in module."""
|
||||
|
||||
from terminaltables.width_and_alignment import max_dimensions, table_width
|
||||
|
||||
|
||||
def test_empty():
|
||||
"""Test with zero-length cells."""
|
||||
assert table_width(max_dimensions([['']])[2], 0, 0) == 0
|
||||
assert table_width(max_dimensions([['', '', '']])[2], 0, 0) == 0
|
||||
assert table_width(max_dimensions([['', '', ''], ['', '', '']])[2], 0, 0) == 0
|
||||
|
||||
assert table_width(max_dimensions([['']], 1, 1)[2], 2, 1) == 4
|
||||
assert table_width(max_dimensions([['', '', '']], 1, 1)[2], 2, 1) == 10
|
||||
assert table_width(max_dimensions([['', '', ''], ['', '', '']], 1, 1)[2], 2, 1) == 10
|
||||
|
||||
|
||||
def test_single_line():
|
||||
"""Test with single-line cells."""
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
]
|
||||
|
||||
# '| Lettuce | green | vegetable |'
|
||||
outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1, 1)[2]
|
||||
assert table_width(outer_widths, outer, inner) == 31
|
||||
|
||||
# ' Lettuce | green | vegetable '
|
||||
outer = 0
|
||||
assert table_width(outer_widths, outer, inner) == 29
|
||||
|
||||
# '| Lettuce green vegetable |'
|
||||
outer, inner = 2, 0
|
||||
assert table_width(outer_widths, outer, inner) == 29
|
||||
|
||||
# ' Lettuce green vegetable '
|
||||
outer = 0
|
||||
assert table_width(outer_widths, outer, inner) == 27
|
||||
|
||||
# '|Lettuce |green |vegetable |'
|
||||
outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1)[2]
|
||||
assert table_width(outer_widths, outer, inner) == 28
|
||||
|
||||
# '|Lettuce |green |vegetable |'
|
||||
outer_widths = max_dimensions(table_data, 3, 2)[2]
|
||||
assert table_width(outer_widths, outer, inner) == 40
|
||||
|
||||
table_data = [
|
||||
['Name', 'Color', 'Type'],
|
||||
['Avocado', 'green', 'nut'],
|
||||
['Tomato', 'red', 'fruit'],
|
||||
['Lettuce', 'green', 'vegetable'],
|
||||
['Watermelon', 'green', 'fruit'],
|
||||
]
|
||||
outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1, 1)[2]
|
||||
assert table_width(outer_widths, outer, inner) == 34
|
||||
|
||||
|
||||
def test_multi_line():
|
||||
"""Test with multi-line cells."""
|
||||
table_data = [
|
||||
['Show', 'Characters'],
|
||||
['Rugrats', ('Tommy Pickles, Chuckie Finster, Phillip DeVille, Lillian DeVille, Angelica Pickles,\n'
|
||||
'Susie Carmichael, Dil Pickles, Kimi Finster, Spike')],
|
||||
['South Park', 'Stan Marsh, Kyle Broflovski, Eric Cartman, Kenny McCormick']
|
||||
]
|
||||
outer, inner, outer_widths = 2, 1, max_dimensions(table_data, 1, 1)[2]
|
||||
assert table_width(outer_widths, outer, inner) == 100
|
59
tests/test_width_and_alignment/test_visible_width.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# coding: utf-8
|
||||
"""Test function in module."""
|
||||
|
||||
import pytest
|
||||
from colorama import Fore
|
||||
from colorclass import Color
|
||||
from termcolor import colored
|
||||
|
||||
from terminaltables.width_and_alignment import visible_width
|
||||
|
||||
|
||||
@pytest.mark.parametrize('string,expected', [
|
||||
# str
|
||||
('hello, world', 12),
|
||||
('世界你好', 8),
|
||||
('蓝色', 4),
|
||||
('שלום', 4),
|
||||
('معرب', 4),
|
||||
('hello 世界', 10),
|
||||
|
||||
# str+ansi
|
||||
('\x1b[34mhello, world\x1b[39m', 12),
|
||||
('\x1b[34m世界你好\x1b[39m', 8),
|
||||
('\x1b[34m蓝色\x1b[39m', 4),
|
||||
('\x1b[34mשלום\x1b[39m', 4),
|
||||
('\x1b[34mمعرب\x1b[39m', 4),
|
||||
('\x1b[34mhello 世界\x1b[39m', 10),
|
||||
|
||||
# colorclass
|
||||
(Color(u'{blue}hello, world{/blue}'), 12),
|
||||
(Color(u'{blue}世界你好{/blue}'), 8),
|
||||
(Color(u'{blue}蓝色{/blue}'), 4),
|
||||
(Color(u'{blue}שלום{/blue}'), 4),
|
||||
(Color(u'{blue}معرب{/blue}'), 4),
|
||||
(Color(u'{blue}hello 世界{/blue}'), 10),
|
||||
|
||||
# colorama
|
||||
(Fore.BLUE + 'hello, world' + Fore.RESET, 12),
|
||||
(Fore.BLUE + '世界你好' + Fore.RESET, 8),
|
||||
(Fore.BLUE + '蓝色' + Fore.RESET, 4),
|
||||
(Fore.BLUE + 'שלום' + Fore.RESET, 4),
|
||||
(Fore.BLUE + 'معرب' + Fore.RESET, 4),
|
||||
(Fore.BLUE + 'hello 世界' + Fore.RESET, 10),
|
||||
|
||||
# termcolor
|
||||
(colored('hello, world', 'blue'), 12),
|
||||
(colored('世界你好', 'blue'), 8),
|
||||
(colored('蓝色', 'blue'), 4),
|
||||
(colored('שלום', 'blue'), 4),
|
||||
(colored('معرب', 'blue'), 4),
|
||||
(colored('hello 世界', 'blue'), 10),
|
||||
])
|
||||
def test(string, expected):
|
||||
"""Test function with different color libraries.
|
||||
|
||||
:param str string: Input string to measure.
|
||||
:param int expected: Expected visible width of string (some characters are len() == 1 but take up 2 spaces).
|
||||
"""
|
||||
assert visible_width(string) == expected
|
77
tox.ini
Normal file
|
@ -0,0 +1,77 @@
|
|||
[general]
|
||||
name = terminaltables
|
||||
|
||||
[tox]
|
||||
envlist = lint,py{34,27,26}
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
python -c "import os, sys; sys.platform == 'win32' and os.system('easy_install pillow')"
|
||||
py.test --cov-report term-missing --cov-report xml --cov {[general]name} --cov-config tox.ini {posargs:tests}
|
||||
deps =
|
||||
colorama==0.3.7
|
||||
colorclass==2.2.0
|
||||
pytest-cov==2.4.0
|
||||
termcolor==1.1.0
|
||||
passenv =
|
||||
WINDIR
|
||||
setenv =
|
||||
PYTHON_EGG_CACHE = {envtmpdir}
|
||||
usedevelop = True
|
||||
|
||||
[testenv:lint]
|
||||
commands =
|
||||
python setup.py check --strict
|
||||
python setup.py check --strict -m
|
||||
python setup.py check --strict -s
|
||||
python setup.py check_version
|
||||
flake8 --application-import-names={[general]name},tests
|
||||
pylint --rcfile=tox.ini setup.py {[general]name}
|
||||
deps =
|
||||
flake8-docstrings==1.0.2
|
||||
flake8-import-order==0.9.2
|
||||
flake8==3.0.4
|
||||
pep8-naming==0.4.1
|
||||
pylint==1.6.4
|
||||
|
||||
[testenv:docs]
|
||||
changedir = {toxinidir}/docs
|
||||
commands =
|
||||
sphinx-build . _build/html {posargs}
|
||||
deps =
|
||||
robpol86-sphinxcontrib-googleanalytics==0.1
|
||||
sphinx-rtd-theme==0.1.10a0
|
||||
sphinx==1.4.8
|
||||
usedevelop = False
|
||||
|
||||
[testenv:docsV]
|
||||
commands =
|
||||
sphinx-versioning push docs gh-pages .
|
||||
deps =
|
||||
{[testenv:docs]deps}
|
||||
sphinxcontrib-versioning==2.2.0
|
||||
passenv =
|
||||
HOME
|
||||
HOSTNAME
|
||||
SSH_AUTH_SOCK
|
||||
TRAVIS*
|
||||
USER
|
||||
|
||||
[flake8]
|
||||
exclude = .tox/*,build/*,docs/*,env/*,get-pip.py
|
||||
import-order-style = smarkets
|
||||
max-line-length = 120
|
||||
statistics = True
|
||||
|
||||
[pylint]
|
||||
disable =
|
||||
locally-disabled,
|
||||
too-few-public-methods,
|
||||
too-many-instance-attributes,
|
||||
ignore = .tox/*,build/*,docs/*,env/*,get-pip.py
|
||||
max-args = 6
|
||||
max-line-length = 120
|
||||
reports = no
|
||||
|
||||
[run]
|
||||
branch = True
|