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
|