From a35389c891ca4ca8aab5d0ae01c5b4f8615375b3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Feb 2025 13:04:33 +0100 Subject: [PATCH] Adding upstream version 3.1.0. Signed-off-by: Daniel Baumann --- .gitignore | 96 ++++++ .travis.yml | 48 +++ CONTRIBUTING.md | 47 +++ LICENSE | 21 ++ README.rst | 162 +++++++++ appveyor.yml | 33 ++ docs/_templates/layout.html | 6 + docs/asciitable.png | Bin 0 -> 44856 bytes docs/asciitable.rst | 16 + docs/changelog.rst | 5 + docs/conf.py | 53 +++ docs/doubletable.png | Bin 0 -> 45812 bytes docs/doubletable.rst | 33 ++ docs/examples.png | Bin 0 -> 72612 bytes docs/favicon.ico | Bin 0 -> 1150 bytes docs/githubtable.png | Bin 0 -> 40167 bytes docs/githubtable.rst | 27 ++ docs/githubtable_rendered.png | Bin 0 -> 53983 bytes docs/index.rst | 77 +++++ docs/install.rst | 38 +++ docs/key.enc | Bin 0 -> 3248 bytes docs/quickstart.rst | 110 ++++++ docs/settings.rst | 79 +++++ docs/singletable.png | Bin 0 -> 44507 bytes docs/singletable.rst | 26 ++ example1.py | 42 +++ example2.py | 86 +++++ example3.py | 36 ++ setup.py | 111 +++++++ terminaltables/__init__.py | 17 + terminaltables/ascii_table.py | 55 +++ terminaltables/base_table.py | 217 ++++++++++++ terminaltables/build.py | 151 +++++++++ terminaltables/github_table.py | 70 ++++ terminaltables/other_tables.py | 177 ++++++++++ terminaltables/terminal_io.py | 98 ++++++ terminaltables/width_and_alignment.py | 160 +++++++++ tests/__init__.py | 5 + tests/screenshot.py | 292 ++++++++++++++++ tests/test_all_tables_e2e/__init__.py | 1 + tests/test_all_tables_e2e/sub_ascii_win10.bmp | Bin 0 -> 36870 bytes tests/test_all_tables_e2e/sub_ascii_winxp.bmp | Bin 0 -> 49350 bytes .../test_all_tables_e2e/sub_double_win10.bmp | Bin 0 -> 36090 bytes .../test_all_tables_e2e/sub_double_win10b.bmp | Bin 0 -> 36090 bytes .../test_all_tables_e2e/sub_double_winxp.bmp | Bin 0 -> 48726 bytes .../test_all_tables_e2e/sub_single_win10.bmp | Bin 0 -> 34854 bytes .../test_all_tables_e2e/sub_single_win10b.bmp | Bin 0 -> 34854 bytes .../test_all_tables_e2e/sub_single_winxp.bmp | Bin 0 -> 45954 bytes tests/test_all_tables_e2e/test_ascii_table.py | 145 ++++++++ .../test_all_tables_e2e/test_double_table.py | 245 ++++++++++++++ .../test_all_tables_e2e/test_github_table.py | 77 +++++ .../test_porcelain_table.py | 59 ++++ .../test_all_tables_e2e/test_single_table.py | 171 ++++++++++ .../test_single_table_windows.py | 246 ++++++++++++++ tests/test_ascii_table.py | 108 ++++++ tests/test_base_table/test_gen_row_lines.py | 86 +++++ tests/test_base_table/test_gen_table.py | 225 +++++++++++++ .../test_base_table/test_horizontal_border.py | 98 ++++++ tests/test_base_table/test_table.py | 196 +++++++++++ tests/test_build/test_build_border.py | 312 ++++++++++++++++++ tests/test_build/test_build_row.py | 104 ++++++ tests/test_build/test_combine.py | 37 +++ tests/test_build/test_flatten.py | 25 ++ tests/test_examples.py | 32 ++ tests/test_terminal_io/__init__.py | 45 +++ .../sub_title_ascii_win10.bmp | Bin 0 -> 1998 bytes .../sub_title_ascii_win2012.bmp | Bin 0 -> 3048 bytes .../sub_title_ascii_winxp.bmp | Bin 0 -> 2070 bytes .../test_terminal_io/sub_title_cjk_win10.bmp | Bin 0 -> 4278 bytes .../sub_title_cjk_win2012.bmp | Bin 0 -> 6896 bytes .../test_terminal_io/sub_title_cjk_winxp.bmp | Bin 0 -> 4322 bytes .../test_terminal_io/test_get_console_info.py | 28 ++ .../test_set_terminal_title.py | 110 ++++++ tests/test_terminal_io/test_terminal_size.py | 54 +++ .../test_align_and_pad_cell.py | 202 ++++++++++++ .../test_column_max_width.py | 107 ++++++ .../test_max_dimensions.py | 100 ++++++ .../test_table_width.py | 70 ++++ .../test_visible_width.py | 59 ++++ tox.ini | 77 +++++ 80 files changed, 5413 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.rst create mode 100644 appveyor.yml create mode 100644 docs/_templates/layout.html create mode 100644 docs/asciitable.png create mode 100644 docs/asciitable.rst create mode 100644 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/doubletable.png create mode 100644 docs/doubletable.rst create mode 100644 docs/examples.png create mode 100644 docs/favicon.ico create mode 100644 docs/githubtable.png create mode 100644 docs/githubtable.rst create mode 100644 docs/githubtable_rendered.png create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/key.enc create mode 100644 docs/quickstart.rst create mode 100644 docs/settings.rst create mode 100644 docs/singletable.png create mode 100644 docs/singletable.rst create mode 100755 example1.py create mode 100755 example2.py create mode 100755 example3.py create mode 100755 setup.py create mode 100644 terminaltables/__init__.py create mode 100644 terminaltables/ascii_table.py create mode 100644 terminaltables/base_table.py create mode 100644 terminaltables/build.py create mode 100644 terminaltables/github_table.py create mode 100644 terminaltables/other_tables.py create mode 100644 terminaltables/terminal_io.py create mode 100644 terminaltables/width_and_alignment.py create mode 100644 tests/__init__.py create mode 100644 tests/screenshot.py create mode 100644 tests/test_all_tables_e2e/__init__.py create mode 100644 tests/test_all_tables_e2e/sub_ascii_win10.bmp create mode 100644 tests/test_all_tables_e2e/sub_ascii_winxp.bmp create mode 100644 tests/test_all_tables_e2e/sub_double_win10.bmp create mode 100644 tests/test_all_tables_e2e/sub_double_win10b.bmp create mode 100644 tests/test_all_tables_e2e/sub_double_winxp.bmp create mode 100644 tests/test_all_tables_e2e/sub_single_win10.bmp create mode 100644 tests/test_all_tables_e2e/sub_single_win10b.bmp create mode 100644 tests/test_all_tables_e2e/sub_single_winxp.bmp create mode 100644 tests/test_all_tables_e2e/test_ascii_table.py create mode 100644 tests/test_all_tables_e2e/test_double_table.py create mode 100644 tests/test_all_tables_e2e/test_github_table.py create mode 100644 tests/test_all_tables_e2e/test_porcelain_table.py create mode 100644 tests/test_all_tables_e2e/test_single_table.py create mode 100644 tests/test_all_tables_e2e/test_single_table_windows.py create mode 100644 tests/test_ascii_table.py create mode 100644 tests/test_base_table/test_gen_row_lines.py create mode 100644 tests/test_base_table/test_gen_table.py create mode 100644 tests/test_base_table/test_horizontal_border.py create mode 100644 tests/test_base_table/test_table.py create mode 100644 tests/test_build/test_build_border.py create mode 100644 tests/test_build/test_build_row.py create mode 100644 tests/test_build/test_combine.py create mode 100644 tests/test_build/test_flatten.py create mode 100644 tests/test_examples.py create mode 100644 tests/test_terminal_io/__init__.py create mode 100644 tests/test_terminal_io/sub_title_ascii_win10.bmp create mode 100644 tests/test_terminal_io/sub_title_ascii_win2012.bmp create mode 100644 tests/test_terminal_io/sub_title_ascii_winxp.bmp create mode 100644 tests/test_terminal_io/sub_title_cjk_win10.bmp create mode 100644 tests/test_terminal_io/sub_title_cjk_win2012.bmp create mode 100644 tests/test_terminal_io/sub_title_cjk_winxp.bmp create mode 100644 tests/test_terminal_io/test_get_console_info.py create mode 100644 tests/test_terminal_io/test_set_terminal_title.py create mode 100644 tests/test_terminal_io/test_terminal_size.py create mode 100644 tests/test_width_and_alignment/test_align_and_pad_cell.py create mode 100644 tests/test_width_and_alignment/test_column_max_width.py create mode 100644 tests/test_width_and_alignment/test_max_dimensions.py create mode 100644 tests/test_width_and_alignment/test_table_width.py create mode 100644 tests/test_width_and_alignment/test_visible_width.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53889c8 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1f9f3ed --- /dev/null +++ b/.travis.yml @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bc22847 --- /dev/null +++ b/CONTRIBUTING.md @@ -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! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d314c3c --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..fe9044f --- /dev/null +++ b/README.rst @@ -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 `_, +`example2.py `_, and +`example3.py `_ + +.. changelog-section-start + +Changelog +========= + +This project adheres to `Semantic Versioning `_. + +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 diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..5b0e517 --- /dev/null +++ b/appveyor.yml @@ -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 diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..f78ddc4 --- /dev/null +++ b/docs/_templates/layout.html @@ -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'] %} diff --git a/docs/asciitable.png b/docs/asciitable.png new file mode 100644 index 0000000000000000000000000000000000000000..97ae2712785b2eae6568e442c19de10ce3c1d0a5 GIT binary patch literal 44856 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 literal 0 HcmV?d00001 diff --git a/docs/asciitable.rst b/docs/asciitable.rst new file mode 100644 index 0000000..d5120f3 --- /dev/null +++ b/docs/asciitable.rst @@ -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 diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..0b9ff7b --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,5 @@ +.. _changelog: + +.. include:: ../README.rst + :start-after: changelog-section-start + :end-before: changelog-section-end diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..fb33f09 --- /dev/null +++ b/docs/conf.py @@ -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') diff --git a/docs/doubletable.png b/docs/doubletable.png new file mode 100644 index 0000000000000000000000000000000000000000..ce532a1b0479b5cec72bad933630915eeb335cfc GIT binary patch literal 45812 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 we6jcB_;7b+2(mV+*iKf#(^b literal 0 HcmV?d00001 diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8cf8947ce400f0ea2c260d722ebf735d37c16efc GIT binary patch literal 1150 zcmX|>eN2^g6vuyU{o$+@Tg4i_#1@SNHu1{Ei75~-B23f^T)5n5mP#?qR+u!Kfss-) zbj?{CG2aj^Ky-kLK=%T^TtIodLWbyoK#|ws1CoaH>D-(8dv>1R^PJ~=cfRMG@9$CS zIk{Y1l-%9b$ImO}s+4+LOkCpm-!GeUz4C%m4tu(4hR|%IFfawT9ze}K*{5Om?=1x6 z71KB}gMAn-48SFcYnPZkRn2TZQ;%JqZ-C~&Iq-FZf_)_4Ev>|sU*hJ!P}}nWn+8`U z=Az*3f5YLg35XhTUt2PevAB?s~oBC zq|!b>hQ&@!&oq(>ozEieqm)!iY%R>ldqUY2vYH3z8lR=O{T|l-hZNenDeoHP+-;4P z!6`NtRkJbwSGFam5^pqW`X#=EEnQ07?o3kJZqY3quS`IL@GkF~Bn#Jd+p}J>~ z6P+Wty3NPSeI79}FWSOserOow2r`etaAxgBln!&m3$ z%x_MJB{VFY`GG%>{CzbMoA$9h;U^j^m;w(*G4(;&&l~%d^eXjBk5WOml^U3*)CUWc bYN$~vGF>UtT&3o|tCZzs`Kxp*Rp#~|J%HF| literal 0 HcmV?d00001 diff --git a/docs/githubtable.png b/docs/githubtable.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2a6a3acd50bb63fc7616084a9c6f2e8ff8cbae GIT binary patch literal 40167 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 literal 0 HcmV?d00001 diff --git a/docs/githubtable.rst b/docs/githubtable.rst new file mode 100644 index 0000000..ba53218 --- /dev/null +++ b/docs/githubtable.rst @@ -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 diff --git a/docs/githubtable_rendered.png b/docs/githubtable_rendered.png new file mode 100644 index 0000000000000000000000000000000000000000..319bbcba74e4e59f5aaffa2f74c4135e55e576bc GIT binary patch literal 53983 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 gLI@$m{p9}!0EvL0Bpmu_!T>> 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 diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..ea0bfae --- /dev/null +++ b/docs/install.rst @@ -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 `_. 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 `_ to the project. + +.. code-block:: bash + + git clone https://github.com/Robpol86/terminaltables.git + cd terminaltables + python setup.py install diff --git a/docs/key.enc b/docs/key.enc new file mode 100644 index 0000000000000000000000000000000000000000..f485db2892002b38197fe31a56fe39c1efc22215 GIT binary patch literal 3248 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 literal 0 HcmV?d00001 diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..83e9ee8 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,110 @@ +.. _quickstart: + +========== +Quickstart +========== + +This section will go over the basics of terminaltables. + +Make sure that you've already :ref:`installed ` 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. diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 0000000..c609d1f --- /dev/null +++ b/docs/settings.rst @@ -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. diff --git a/docs/singletable.png b/docs/singletable.png new file mode 100644 index 0000000000000000000000000000000000000000..cc595ff783e55044ab3e1c86c1a02ab770fb9c53 GIT binary patch literal 44507 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 literal 0 HcmV?d00001 diff --git a/docs/singletable.rst b/docs/singletable.rst new file mode 100644 index 0000000..9d6313b --- /dev/null +++ b/docs/singletable.rst @@ -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 diff --git a/example1.py b/example1.py new file mode 100755 index 0000000..daf1fbf --- /dev/null +++ b/example1.py @@ -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() diff --git a/example2.py b/example2.py new file mode 100755 index 0000000..51644f8 --- /dev/null +++ b/example2.py @@ -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() diff --git a/example3.py b/example3.py new file mode 100755 index 0000000..bec5500 --- /dev/null +++ b/example3.py @@ -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() diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..0bc7bf2 --- /dev/null +++ b/setup.py @@ -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, + ) diff --git a/terminaltables/__init__.py b/terminaltables/__init__.py new file mode 100644 index 0000000..6cea813 --- /dev/null +++ b/terminaltables/__init__.py @@ -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' diff --git a/terminaltables/ascii_table.py b/terminaltables/ascii_table.py new file mode 100644 index 0000000..3623918 --- /dev/null +++ b/terminaltables/ascii_table.py @@ -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) diff --git a/terminaltables/base_table.py b/terminaltables/base_table.py new file mode 100644 index 0000000..281d5a3 --- /dev/null +++ b/terminaltables/base_table.py @@ -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)) diff --git a/terminaltables/build.py b/terminaltables/build.py new file mode 100644 index 0000000..6b23b2f --- /dev/null +++ b/terminaltables/build.py @@ -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) diff --git a/terminaltables/github_table.py b/terminaltables/github_table.py new file mode 100644 index 0000000..7eb1be7 --- /dev/null +++ b/terminaltables/github_table.py @@ -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) diff --git a/terminaltables/other_tables.py b/terminaltables/other_tables.py new file mode 100644 index 0000000..50c0bcd --- /dev/null +++ b/terminaltables/other_tables.py @@ -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 diff --git a/terminaltables/terminal_io.py b/terminaltables/terminal_io.py new file mode 100644 index 0000000..8b8c10d --- /dev/null +++ b/terminaltables/terminal_io.py @@ -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 diff --git a/terminaltables/width_and_alignment.py b/terminaltables/width_and_alignment.py new file mode 100644 index 0000000..057e800 --- /dev/null +++ b/terminaltables/width_and_alignment.py @@ -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 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..b91337b --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,5 @@ +"""Allows importing from screenshot.""" + +import py + +PROJECT_ROOT = py.path.local(__file__).dirpath().join('..') diff --git a/tests/screenshot.py b/tests/screenshot.py new file mode 100644 index 0000000..6ccb593 --- /dev/null +++ b/tests/screenshot.py @@ -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) diff --git a/tests/test_all_tables_e2e/__init__.py b/tests/test_all_tables_e2e/__init__.py new file mode 100644 index 0000000..785cc5a --- /dev/null +++ b/tests/test_all_tables_e2e/__init__.py @@ -0,0 +1 @@ +"""Allows importing from screenshot.""" diff --git a/tests/test_all_tables_e2e/sub_ascii_win10.bmp b/tests/test_all_tables_e2e/sub_ascii_win10.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fe21fa7f0cf8de9c27b926f94fdc68142fcd187c GIT binary patch literal 36870 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 Of0Q}z`p)0{UjGMe*Uc1knf#G-RX>4$S$J|5kX zm!{WdpluF4x+B-N=<)Wwac;|}ZS=>h|1~2%qsqN;Zp+7`JJNsOE_43naQN@?`seNq zD)YG~1&{8?J>%cTcbW4ahXY>RehcLbcH8$dpWDWJ@6Dq-^1enon#U7ouj4!)4ksX* z8wvqB&p4Wg2{uRAI6^-#n#S>P9AGn^P%CYiVc`&)dnO+DHJsnZDF!TtEjAyp&|Sls z0US}Szb<}g8TLg!Xy$Q93p=nx>p78BR(T5P8YV0RrPNJJg;cn#8z|2q?78UC9r0QU z!Beb~nSq6L;i(J}XH=iiuHyh?oY4xrpSM-dmy>VrWzMT(Q|izYgoaFUa|CBq>lT_K zlc_(S#+V$43q<)rOeTDHHp=AI?6NGX8`q%YvKp#eZg zR7HJ5JT8SIA1DY#*+z{}wUl`3;UjLF?>>{rCSLI_VbAX#-I1&c4qa2XDUMUGvKy)4 zyxaMXibXz2(|N`igmY=SBhYFUkM787#Ul99^yXp}b4~+S(2+`44b6Jv+?G$<=ncEU zmkRxiD)+{@Egz5WNdH~BH_mPOcyvcznqHfMwmJ0Zj;yvQ`%tIpC+AH(i#-nU0T>f5 zOq}Ow;8K%5#$yVW&A&1~tDzmNUFHvYbSQ zS#xkGOZri*?Lr-AXhg|y$z{P61j04kyK9Q3>ej;>R@O$Hh5D-onY|7@8;;007NVxW zamX=9QO4njuaIyf7>9HZ1*LdDrHzIy4D=mq<|jtnDA#d@rg(8|^6shosl*imvpbGc z5XQv}-;HM1-q=QHs_#!DUMzby9Lb!Zdr>~_buY-wY7Vn;T2p1!rpS4<)+Tu0af;Bz z-W$iM`ury6^GVN!BQ<{Z*?eV9_hqc6mMiE;ji0tQy>V{Kr)~7MJ>ly_e@2yie0rcOi4~&j$ze>U@QiADN_BZU7w^)S zy5mH)Xvsiy&J|!GDDa72_mh#~VUcrMy!RypsY5Rihzo>@;RsHt)(13YgxETiISJJ& zgd^g$UGQu;l6jy3z(@%~Cs8lY^zx7u3944~rhdqAs_L+z%I+stSGmYJSwagARf#Kg z=m@&Uv5Z4U=!f<<)s!P8t^hm2u*JP+!x4AF@rVULULMi4@J$RxCe!M z>Uw^aD`q%FP2G=b#X^~L9C4Bo1?r1EUeShnIfAIr9XK{rXJI*tyE31}I<}6z7YST0 ze(Cb-NDQ%2_UMjy^)#SPXqdQXL=jJw_UelXrOb%T^M`YY> zp*PNL`LvDRZ72C;#GX;*-Z;1AS#EdAvr~M(mAqTRt9L2$q1JXv5@PGxQ#4x8=j=Sb^v0pr+T3l$U*P89)!uH0`R- z*NnTiV{K$D=#ae-H1Cb6Y+h-I153*Jhw?4n4XfS&P!cNbi4pHa=Oz z=ZH8J!XB1-l1g)4Jwcjz-hEi)49}pUStO@CdTKE?hc^1(z|2NZ(`{2N@`+PL<`Y%; zuoeg~dPMY?kXnHm==V5ybVstfd}=J1?7`zu@$n31=LnwhCTe{YfVI&>PXqge>Wct zOnv0u7aFxZx+A-tKdfSJoZIqg8~tITfAK)isPdUJ?o!LYAvagBEgx6Vk>)8X5@?%4 zr`MK`E9i)G?hR?1L(fKAKCYl6&bc?FZ4NyfZTYx@jyUJukhVGWY_#R$3JSs8{E0TK vt7z4w2ik4Uc1knf#G-RX>4$S$J|5kX zm!{WdpluF4x+B-N=<)Wwac;|}ZS=>h|1~2%qsqN;Zp+7`JJNsOE_43naQN@?`seNq zD)YG~1&{8?J>%cTcbW4ahXY>RehcLbcH8$dpWDWJ@6Dq-^1enon#U7ouj4!)4ksX* z8wvqB&p4Wg2{uRAI6^-#n#S>P9AGn^P%CYiVc`&)dnO+DHJsnZDF!TtEjAyp&|Sls z0US}Szb<}g8TLg!Xy$Q93p=nx>p78BR(T5P8YV0RrPNJJg;cn#8z|2q?78UC9r0QU z!Beb~nSq6L;i(J}XH=iiuHyh?oY4xrpSM-dmy>VrWzMT(Q|izYgoaFUa|CBq>lT_K zlc_(S#+V$43q<)rOeTDHHp=AI?6NGX8`q%YvKp#eZg zR7HJ5JT8SIA1DY#*+z{}wUl`3;UjLF?>>{rCSLI_VbAX#-I1&c4qa2XDUMUGvKy)4 zyxaMXibXz2(|N`igmY=SBhYFUkM787#Ul99^yXp}b4~+S(2+`44b6Jv+?G$<=ncEU zmkRxiD)+{@Egz5WNdH~BH_mPOcyvcznqHfMwmJ0Zj;yvQ`%tIpC+AH(i#-nU0T>f5 zOq}Ow;8K%5#$yVW&A&1~tDzmNUFHvYbSQ zS#xkGOZri*?Lr-AXhg|y$z{P61j04kyK9Q3>ej;>R@O$Hh5D-onY|7@8;;007NVxW zamX=9QO4njuaIyf7>9HZ1*LdDrHzIy4D=mq<|jtnDA#d@rg(8|^6shosl*imvpbGc z5XQv}-;HM1-q=QHs_#!DUMzby9Lb!Zdr>~_buY-wY7Vn;T2p1!rpS4<)+Tu0af;Bz z-W$iM`ury6^GVN!BQ<{Z*?eV9_hqc6mMiE;ji0tQy>V{Kr)~7MJ>ly_e@2yie0rcOi4~&j$ze>U@QiADN_BZU7w^)S zy5mH)Xvsiy&J|!GDDa72_mh#~VUcrMy!RypsY5Rihzo>@;RsHt)(13YgxETiISJJ& zgd^g$UGQu;l6jy3z(@%~Cs8lY^zx7u3944~rhdqAs_L+z%I+stSGmYJSwagARf#Kg z=m@&Uv5Z4U=!f<<)s!P8t^hm2u*JP+!x4AF@rVULULMi4@J$RxCe!M z>Uw^aD`q%FP2G=b#X^~L9C4Bo1?r1EUeShnIfAIr9XK{rXJI*tyE31}I<}6z7YST0 ze(Cb-NDQ%2_UMjy^)#SPXqdQXL=jJw_UelXrOb%T^M`YY> zp*PNL`LvDRZ72C;#GX;*-Z;1AS#EdAvr~M(mAqTRt9L2$q1JXv5@PGxQ#4x8=j=Sb^v0pr+T3l$U*P89)!uH0`R- z*NnTiV{K$D=#ae-H1Cb6Y+h-I153*Jhw?4n4XfS&P!cNbi4pHa=Oz z=ZH8J!XB1-l1g)4Jwcjz-hEi)49}pUStO@CdTKE?hc^1(z|2NZ(`{2N@`+PL<`Y%; zuoeg~dPMY?kXnHm==V5ybVstfd}=J1?7`zu@$n31=LnwhCTe{YfVI&>PXqge>Wct zOnv0u7aFxZx+A-tKdfSJoZIqg8~tITfAK)isPdUJ?o!LYAvagBEgx6Vk>)8X5@?%4 zr`MK`E9i)G?hR?1L(fKAKCYl6&bc?FZ4NyfZTYx@jyUJukhVGWY_#R$3JT#;3HDF1 wVO>S5E^ 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 literal 0 HcmV?d00001 diff --git a/tests/test_all_tables_e2e/sub_single_win10.bmp b/tests/test_all_tables_e2e/sub_single_win10.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ff6f272a301fc5fc1277a30b99e441efbb30b2a6 GIT binary patch literal 34854 zcmeHPJC^IV5|nr075MlZFaP`DkIQ`gg!7O1{R_Xp;`h7D_c&iJ|NcIIyicDy0@HB4zsNBG z=M+wz9R1+A+{R^;BA2hPf>s@7+^<=W#*T8|JonymwD65$pT2H_UDEc<-L- zJC6&x-Y~bt)7JZvllSL25qQPy_J+AF9`D^#=Zu-7tyVDqe7XF${QPsY@0`5~o}9{o zJn!982f8`YR0Z>&mkWM){ASt<@9@tGp2PilgWkKRyyQ&X#~bJGR9UwE_pcRhW2(56WmdTIKRm@-N6NQR$#gP^&a;`F_gOnq! zzZW}{^ye}jbn`k)(7;R|z2|ISQRO9sYnZSm5JET23Zaq2*}MIqymwD!6l9*)=H-Wg z&K^LZoRA-Jgcs@)`g9$@8Sm%;K0l7E<>jLLtYFU8q%uQGNHS7nkCcO}(mEoah-4Vc zx6uX%i3LaTflmf}YcwjcSSO#P~PoSmFMV6zmiL7D{_fnM~(+G~3Gz30nC;(>4 zk&4EJK$u1u53+}BvX7EfPstCXd&Ikidk2w>z0(=DA5GbuX-}onnS7=~)%r|T_He40 z_ZZ)xTgDUedB3CfV7V;0!F6Ku5D)Ks@;zc(LUwBT40>v(=_jf8hPf@Cw%(tV@^9A7 zD`vMh%x&>_@1A;%Vci?%ws^dEPrdWGB?4_@=)HR?-0>f5@8KXBKLi%_WXXP`ywqX^aZb{nN+JmfKn$L7aKpZqq%qM!vWfl%| z&1`Hw)rYXK;Fq;^=~2UG$LDH|hSdgA;P<3pA?ph)Q&Dy8&>y$aGFmQCmLAx z!J&MjP*2P!&>|FA`N1C_q9DvL+)3+wj$0&rG(448v@$@Xy9DbBSHZy1wyaom zQCCts(yBHro(ICn57UL)EE{R|z~P$g>G_-!`SG|xm{QDo7$NgnfmB5t9}Q1Q$TcDv zkgPqXZXB-oGZ1e4;}D*ifMcJZlu@}tKz}2R>^PT2BEplCMLzMvnnb#A==rI{iUY9| z>l05O+{Ey=&FFq>VI!aV`={lvY5Qn+D%M98F?Bra-XF`YpriU+q47|pjQOG3!wcuu zC)@%s188qv7LKh?jdv?hX!~e*s-U*_(zl$?y|F9m`wV)jsMyWL-Y~bt)7E=8gW{6{ zdByDZhPf>s@7+_cF=#88?R%?UX3y+d1<#qTVcUE6)XA<7GGD>Wxe9&leRW-hxPoVu zjqO6-yQiF0yBdHCTs@!!+V<63(@V{THPNA%osnvZl3I8@pT@9PiZ1wGFFOv@Xg+~o^}#r#oG*Q;wR|)@mCf{m?5LN3_cA>8QrCbHWCFV1=Bdw@W!E7r{2|i~7t$e~SRH6zQjf@p$i^dW})q8|JonymwE%^SLDg zZDZ)YdumH>p0Cpz=C*jecTYV(H(NaRhPf>s@7+^dqWgTE-Y~btAP&)zV%#pAtus_#55=z7E47LWJtsU>24fA)sCEgtXPQ+?-gLDw7Rws_il zUvl#PJSPILnBCqmx5eYVd+MAqbF|goFt^3yy?g5D$e&}cH_UDEc<-Ky;~zE;`R{JE zJAt_GhZb;#q;@@lgGrlr`R=H1v_Hb4{txW2FKgahVwg>O{ zcX93w<~8&}Awkl#nquvwGUw%!sH}JOK~A}g%=SVuQN!EuR3sV(II3SJu66!~Gw(-4pPi?dKY_0v6 zvlX(r8{6=pAoXg!*)=-BYhIrhCKO z7LWJtsdqlNM4)X9y?0M->CN+Xdc)inkN57W=jUdN$KEiv#pAtuYD;vVuhSdmws^dE XP*_BHA1@sNyKeaW@_KtbD+2!q(^ZsI literal 0 HcmV?d00001 diff --git a/tests/test_all_tables_e2e/sub_single_win10b.bmp b/tests/test_all_tables_e2e/sub_single_win10b.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c8d1e361bd633004867bcbb79d5bbcd0ddc608c9 GIT binary patch literal 34854 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 literal 0 HcmV?d00001 diff --git a/tests/test_all_tables_e2e/test_ascii_table.py b/tests/test_all_tables_e2e/test_ascii_table.py new file mode 100644 index 0000000..51ebc2a --- /dev/null +++ b/tests/test_all_tables_e2e/test_ascii_table.py @@ -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) diff --git a/tests/test_all_tables_e2e/test_double_table.py b/tests/test_all_tables_e2e/test_double_table.py new file mode 100644 index 0000000..892357a --- /dev/null +++ b/tests/test_all_tables_e2e/test_double_table.py @@ -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) diff --git a/tests/test_all_tables_e2e/test_github_table.py b/tests/test_all_tables_e2e/test_github_table.py new file mode 100644 index 0000000..6176215 --- /dev/null +++ b/tests/test_all_tables_e2e/test_github_table.py @@ -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 diff --git a/tests/test_all_tables_e2e/test_porcelain_table.py b/tests/test_all_tables_e2e/test_porcelain_table.py new file mode 100644 index 0000000..7677188 --- /dev/null +++ b/tests/test_all_tables_e2e/test_porcelain_table.py @@ -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 diff --git a/tests/test_all_tables_e2e/test_single_table.py b/tests/test_all_tables_e2e/test_single_table.py new file mode 100644 index 0000000..f4fa6b9 --- /dev/null +++ b/tests/test_all_tables_e2e/test_single_table.py @@ -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 diff --git a/tests/test_all_tables_e2e/test_single_table_windows.py b/tests/test_all_tables_e2e/test_single_table_windows.py new file mode 100644 index 0000000..a15fa3a --- /dev/null +++ b/tests/test_all_tables_e2e/test_single_table_windows.py @@ -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) diff --git a/tests/test_ascii_table.py b/tests/test_ascii_table.py new file mode 100644 index 0000000..020a443 --- /dev/null +++ b/tests/test_ascii_table.py @@ -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 diff --git a/tests/test_base_table/test_gen_row_lines.py b/tests/test_base_table/test_gen_row_lines.py new file mode 100644 index 0000000..0d0f43c --- /dev/null +++ b/tests/test_base_table/test_gen_row_lines.py @@ -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 diff --git a/tests/test_base_table/test_gen_table.py b/tests/test_base_table/test_gen_table.py new file mode 100644 index 0000000..54d5fe1 --- /dev/null +++ b/tests/test_base_table/test_gen_table.py @@ -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 diff --git a/tests/test_base_table/test_horizontal_border.py b/tests/test_base_table/test_horizontal_border.py new file mode 100644 index 0000000..e162261 --- /dev/null +++ b/tests/test_base_table/test_horizontal_border.py @@ -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 diff --git a/tests/test_base_table/test_table.py b/tests/test_base_table/test_table.py new file mode 100644 index 0000000..c5b5a89 --- /dev/null +++ b/tests/test_base_table/test_table.py @@ -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 diff --git a/tests/test_build/test_build_border.py b/tests/test_build/test_build_border.py new file mode 100644 index 0000000..9c410fd --- /dev/null +++ b/tests/test_build/test_build_border.py @@ -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, ''), + ([3, 3, 3], 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 diff --git a/tests/test_build/test_build_row.py b/tests/test_build/test_build_row.py new file mode 100644 index 0000000..ce55944 --- /dev/null +++ b/tests/test_build/test_build_row.py @@ -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 diff --git a/tests/test_build/test_combine.py b/tests/test_build/test_combine.py new file mode 100644 index 0000000..b296ffd --- /dev/null +++ b/tests/test_build/test_combine.py @@ -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 == ['>', '<'] diff --git a/tests/test_build/test_flatten.py b/tests/test_build/test_flatten.py new file mode 100644 index 0000000..aacfdbd --- /dev/null +++ b/tests/test_build/test_flatten.py @@ -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 diff --git a/tests/test_examples.py b/tests/test_examples.py new file mode 100644 index 0000000..f0799f9 --- /dev/null +++ b/tests/test_examples.py @@ -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 diff --git a/tests/test_terminal_io/__init__.py b/tests/test_terminal_io/__init__.py new file mode 100644 index 0000000..93738fc --- /dev/null +++ b/tests/test_terminal_io/__init__.py @@ -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 diff --git a/tests/test_terminal_io/sub_title_ascii_win10.bmp b/tests/test_terminal_io/sub_title_ascii_win10.bmp new file mode 100644 index 0000000000000000000000000000000000000000..638d0a3cc997b23e6da65cb07f8582b1094693eb GIT binary patch literal 1998 zcmbW2%~1p)5QXt%nSmh~fd`JByqE^@a5NIa*aQe>{Ft|PJ(=2BR;q?-nn(BRADEA? z%X8YtJM$agC*KR-Q+j1i>HE6BFRs(JZK*UCtt8~yx0JH>(3Hk9ZeUcw2s!i; zu&@%>L-d@gcL*G59%&*)4t8$P4va$ZT^Sh_ypgx{xZ;l7rRP*#rZ!Mz_N(E5iFwTw zx>J!%6YlK?Km~$>8jrXH^Rvi{ak=-q^row=+`>e?6vRe;h;XRK@LOxNMj zddY%&T&L$rn9_J$o`BVg-jJl!-xGzeCuOpX*_Hb>ojNX^p36t%q&WePT4cucY2~0- t2bp(IO74i-Qp@%Ca3XVDp3pmj*ERfTrv9X4>LPz3y|-8XU+V-M`2)ULi3$J! literal 0 HcmV?d00001 diff --git a/tests/test_terminal_io/sub_title_ascii_win2012.bmp b/tests/test_terminal_io/sub_title_ascii_win2012.bmp new file mode 100644 index 0000000000000000000000000000000000000000..04f0f2aa7073ec757d46b231df0561110b9cb2cb GIT binary patch literal 3048 zcmb`Gu}#E46hwV784)E=0U-erAq7B;$RIk=2DLy&3(yE6p##L=e>}#s_R4~@@M`|d zo4?jcoL#&f4C8a6^H}ed-fwF&9O)c}Ck=q9FxQxfD%QvnuV7M{!kStfBuXvZ0>J=q3!=#WSoL7o86q%7g!68q$KRbvxNC2t z9dHe>v}6tZA13ZyTE%xdfWF@Sv&>SM2=%DSbPZGf2oJl3uuA6A{_VLE&s}>IKD%Qvqw~@}(hW04 zR!Ml|+L=B^3*lYN`)#a=5fCxE#!WO8L>u8qv>2&Jt^rC!1$GDA6YlIebFu%vKPHQQ0-?gum?nFVx zNb&PwFKc2wqwe9VCDVZs|NV`L5dE)pjO*42iZ8co@IN!UhpT)^`J5g}T| literal 0 HcmV?d00001 diff --git a/tests/test_terminal_io/sub_title_ascii_winxp.bmp b/tests/test_terminal_io/sub_title_ascii_winxp.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c40a2d21fe75dd20a61d6b49d817df0ffe25bf55 GIT binary patch literal 2070 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'], + ['\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]) diff --git a/tests/test_width_and_alignment/test_table_width.py b/tests/test_width_and_alignment/test_table_width.py new file mode 100644 index 0000000..5818789 --- /dev/null +++ b/tests/test_width_and_alignment/test_table_width.py @@ -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 diff --git a/tests/test_width_and_alignment/test_visible_width.py b/tests/test_width_and_alignment/test_visible_width.py new file mode 100644 index 0000000..79cebcb --- /dev/null +++ b/tests/test_width_and_alignment/test_visible_width.py @@ -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 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..f5b8ad4 --- /dev/null +++ b/tox.ini @@ -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