Merge branch 'master' of github.com:willmcgugan/rich into feat/add-live-table-proposal

This commit is contained in:
Nathan Page 2020-12-02 23:46:02 -08:00
commit 1020405830
48 changed files with 1276 additions and 182 deletions

View File

@ -9,7 +9,9 @@ jobs:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
python-version: [3.6, 3.7, 3.8, 3.9]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
@ -17,22 +19,22 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Install and configure Poetry
uses: snok/install-poetry@v1.1.1
with:
version: 1.1.4
virtualenvs-create: false
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
poetry install
run: poetry install
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
- name: Format check with black
run: |
make format-check
run: make format-check
- name: Typecheck with mypy
run: |
make typecheck
run: make typecheck
- name: Test with pytest
run: |
pip install .
python -m pytest tests -v --cov=./rich --cov-report=xml:./coverage.xml --cov-report term-missing
- name: Upload code coverage
uses: codecov/codecov-action@v1.0.10
with:

View File

@ -5,13 +5,44 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [9.2.0] - Unreleased
## [9.3.0] - 2020-12-1
### Added
- Added get_datetime parameter to Console, to allow for repeatable tests
- Added get_time parameter to Console
- Added rich.abc.RichRenderable
- Added expand_all to rich.pretty.install()
- Added locals_max_length, and locals_max_string to Traceback and logging.RichHandler
- Set defaults of max_length and max_string for Traceback to 10 and 80
- Added disable argument to Progress
### Changed
- Reformatted test card (python -m rich)
### Fixed
- Fixed redirecting of stderr in Progress
- Fixed broken expanded tuple of one https://github.com/willmcgugan/rich/issues/445
- Fixed justify argument not working in console.log https://github.com/willmcgugan/rich/issues/460
## [9.2.0] - 2020-11-08
### Added
- Added tracebacks_show_locals parameter to RichHandler
- Applied dim=True to indent guide styles
- Added max_string to Pretty
- Added rich.ansi.AnsiDecoder
- Added decoding of ansi codes to captured stdout in Progress
- Added expand_all to rich.pretty.pprint
### Changed
- Applied dim=True to indent guide styles
- Factored out RichHandler.get_style_and_level to allow for overriding in subclasses
- Hid progress bars from html export
- rich.pretty.pprint now soft wraps
## [9.1.0] - 2020-10-23

View File

@ -1,13 +1,24 @@
# Contributing to Rich
This project welcomes contributions in the form of Pull Requests. For clear bug-fixes / typos etc. just submit a PR. For new features or if there is any doubt in how to fix a bug, you might want to open an issue prior to starting work, or email willmcgugan+rich@gmail.com to discuss it first.
This project welcomes contributions in the form of Pull Requests.
For clear bug-fixes / typos etc. just submit a PR.
For new features or if there is any doubt in how to fix a bug, you might want
to open an issue prior to starting work, or email willmcgugan+rich@gmail.com
to discuss it first.
## Development Environment
To start developing with Rich, first create a _virtual environment_ then run the following to install development requirements:
Rich uses [poetry](https://python-poetry.org/docs/) for packaging and
dependency management. To start developing with Rich, install Poetry
using the [recommended method](https://python-poetry.org/docs/#installation) or run:
```
pip install poetry
```
Once Poetry is installed, install the dependencies with the following command:
```
pip install -r requirements-dev.txt
poetry install
```
@ -29,7 +40,8 @@ New code should ideally have tests and not break existing tests.
### Type Checking
Rich uses type annotations throughout, and `mypy` to do the checking. Run the following to type check Rich:
Rich uses type annotations throughout, and `mypy` to do the checking.
Run the following to type check Rich:
```
make typecheck
@ -49,4 +61,4 @@ Rich uses [`black`](https://github.com/psf/black) for code formatting.
I recommend setting up black in your editor to format on save.
To run black from the command line, use `make format-check` to check your formatting,
and use `make format` to format and write to the files.
and use `make format` to format and write to the files.

View File

@ -31,6 +31,12 @@ Install with `pip` or your favorite PyPi package manager.
pip install rich
```
Run the following to test Rich output on your terminal:
```
python -m rich
```
## Rich print function
To effortlessly add rich output to your application, you can import the [rich print](https://rich.readthedocs.io/en/latest/introduction.html#quick-start) method, which has the same signature as the builtin Python function. Try this:
@ -316,7 +322,7 @@ Here are a few projects using Rich:
Browse GitHub trending projects from your command line
- [intel/cve-bin-tool](https://github.com/intel/cve-bin-tool)
This tool scans for a number of common, vulnerable components (openssl, libpng, libxml2, expat and a few others) to let you know if your system includes common libraries with known vulnerabilities.
- [nf-core/tools](https://github.com/nf)
- [nf-core/tools](https://github.com/nf-core/tools)
Python package with helper tools for the nf-core community.
- [cansarigol/pdbr](https://github.com/cansarigol/pdbr)
pdb + Rich library for enhanced debugging

View File

@ -1,3 +1,3 @@
alabaster==0.7.12
Sphinx==3.2.1
Sphinx==3.3.1
sphinx-rtd-theme==0.5.0

View File

@ -233,7 +233,7 @@ You can page output from a Console by calling :meth:`~rich.console.Console.pager
with console.pager():
console.print(make_test_card())
Since the default pager on most platforms don't support color, Rich will strip color from the output. If you know that your pager supports color, you can set ``style=True`` when calling the :meth:`~rich.console.Console.pager` method.
Since the default pager on most platforms don't support color, Rich will strip color from the output. If you know that your pager supports color, you can set ``styles=True`` when calling the :meth:`~rich.console.Console.pager` method.
.. note::
Rich will use the ``PAGER`` environment variable to get the pager command. On Linux and macOS you can set this to ``less -r`` to enable paging with ANSI styles.

View File

@ -4,7 +4,6 @@ Reference
.. toctree::
:maxdepth: 3
reference/init.rst
reference/align.rst
reference/bar.rst
reference/color.rst
@ -12,6 +11,7 @@ Reference
reference/console.rst
reference/emoji.rst
reference/highlighter.rst
reference/init.rst
reference/live.rst
reference/logging.rst
reference/markdown.rst
@ -19,9 +19,11 @@ Reference
reference/measure.rst
reference/padding.rst
reference/panel.rst
reference/progress.rst
reference/pretty.rst
reference/progress_bar.rst
reference/progress.rst
reference/prompt.rst
reference/protocol.rst
reference/rule.rst
reference/segment.rst
reference/style.rst
@ -31,3 +33,4 @@ Reference
reference/text.rst
reference/theme.rst
reference/traceback.rst
references/abc.rst

View File

@ -0,0 +1,7 @@
rich.abc
========
.. automodule:: rich.abc
:members:

View File

@ -0,0 +1,6 @@
rich.pretty
===========
.. automodule:: rich.pretty
:members:

View File

@ -0,0 +1,5 @@
rich.protocol
=============
.. automodule:: rich.protocol
:members:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 KiB

After

Width:  |  Height:  |  Size: 718 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

After

Width:  |  Height:  |  Size: 807 KiB

380
poetry.lock generated
View File

@ -1,3 +1,11 @@
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "appnope"
version = "0.1.0"
@ -6,12 +14,20 @@ category = "main"
optional = true
python-versions = "*"
[[package]]
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "19.3.0"
description = "Classes Without Boilerplate"
category = "main"
optional = true
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
@ -28,6 +44,29 @@ category = "main"
optional = true
python-versions = "*"
[[package]]
name = "black"
version = "20.8b1"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
appdirs = "*"
click = ">=7.1.2"
dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
mypy-extensions = ">=0.4.3"
pathspec = ">=0.6,<1"
regex = ">=2020.1.8"
toml = ">=0.10.1"
typed-ast = ">=1.4.0"
typing-extensions = ">=3.7.4"
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
name = "bleach"
version = "3.1.5"
@ -41,6 +80,14 @@ packaging = "*"
six = ">=1.9.0"
webencodings = "*"
[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "colorama"
version = "0.4.4"
@ -58,11 +105,22 @@ optional = false
python-versions = "*"
[package.extras]
test = ["flake8 (3.7.8)", "hypothesis (3.55.3)"]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "coverage"
version = "5.3"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
toml = ["toml"]
[[package]]
name = "dataclasses"
version = "0.7"
version = "0.8"
description = "A backport of the dataclasses module for Python 3.6"
category = "main"
optional = false
@ -97,7 +155,7 @@ name = "importlib-metadata"
version = "1.7.0"
description = "Read metadata from Python packages"
category = "main"
optional = true
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[package.dependencies]
@ -107,6 +165,14 @@ zipp = ">=0.5"
docs = ["sphinx", "rst.linker"]
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "ipykernel"
version = "5.3.2"
@ -194,7 +260,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
parso = ">=0.7.0,<0.8.0"
[package.extras]
qa = ["flake8 (3.7.9)"]
qa = ["flake8 (==3.7.9)"]
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"]
[[package]]
@ -275,6 +341,30 @@ category = "main"
optional = true
python-versions = "*"
[[package]]
name = "mypy"
version = "0.790"
description = "Optional static typing for Python"
category = "dev"
optional = false
python-versions = ">=3.5"
[package.dependencies]
mypy-extensions = ">=0.4.3,<0.5.0"
typed-ast = ">=1.4.0,<1.5.0"
typing-extensions = ">=3.7.4"
[package.extras]
dmypy = ["psutil (>=4.0)"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "nbconvert"
version = "5.6.1"
@ -351,7 +441,7 @@ name = "packaging"
version = "20.4"
description = "Core utilities for Python packages"
category = "main"
optional = true
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
@ -377,6 +467,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
testing = ["docopt", "pytest (>=3.0.7)"]
[[package]]
name = "pathspec"
version = "0.8.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pexpect"
version = "4.8.0"
@ -396,6 +494,20 @@ category = "main"
optional = true
python-versions = "*"
[[package]]
name = "pluggy"
version = "0.13.1"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
name = "prometheus-client"
version = "0.8.0"
@ -426,6 +538,14 @@ category = "main"
optional = true
python-versions = "*"
[[package]]
name = "py"
version = "1.9.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pygments"
version = "2.7.2"
@ -439,7 +559,7 @@ name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
category = "main"
optional = true
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
@ -453,6 +573,44 @@ python-versions = "*"
[package.dependencies]
six = "*"
[[package]]
name = "pytest"
version = "6.1.2"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.5"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=17.4.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.8.2"
toml = "*"
[package.extras]
checkqa_mypy = ["mypy (==0.780)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "2.10.1"
description = "Pytest plugin for measuring coverage."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
coverage = ">=4.4"
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"]
[[package]]
name = "python-dateutil"
version = "2.8.1"
@ -488,6 +646,14 @@ category = "main"
optional = true
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
[[package]]
name = "regex"
version = "2020.11.13"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "send2trash"
version = "1.5.0"
@ -501,7 +667,7 @@ name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = true
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
@ -528,6 +694,14 @@ python-versions = "*"
[package.extras]
test = ["pathlib2"]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tornado"
version = "6.0.4"
@ -552,6 +726,14 @@ six = "*"
[package.extras]
test = ["pytest", "mock"]
[[package]]
name = "typed-ast"
version = "1.4.1"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "typing-extensions"
version = "3.7.4.3"
@ -592,7 +774,7 @@ name = "zipp"
version = "3.1.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = true
optional = false
python-versions = ">=3.6"
[package.extras]
@ -605,13 +787,21 @@ jupyter = ["ipywidgets"]
[metadata]
lock-version = "1.1"
python-versions = "^3.6"
content-hash = "64c8b886ebf3d4fec7647f65af7ebf6b20e031fbec205d96af3c937ff686c415"
content-hash = "2aac9442e8c6265a9815532d55ada0835532f15a507a6c96445c5f8937f7d3f7"
[metadata.files]
appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
appnope = [
{file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
{file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
@ -620,10 +810,17 @@ backcall = [
{file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
]
black = [
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
]
bleach = [
{file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"},
{file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
@ -632,9 +829,45 @@ commonmark = [
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
coverage = [
{file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"},
{file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"},
{file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"},
{file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"},
{file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"},
{file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"},
{file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"},
{file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"},
{file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"},
{file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"},
{file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"},
{file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"},
{file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"},
{file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"},
{file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"},
{file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"},
{file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"},
{file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"},
{file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"},
{file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"},
{file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"},
{file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"},
{file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"},
{file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"},
{file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"},
{file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"},
{file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"},
{file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"},
{file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"},
{file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"},
{file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"},
{file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"},
{file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"},
{file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"},
]
dataclasses = [
{file = "dataclasses-0.7-py3-none-any.whl", hash = "sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836"},
{file = "dataclasses-0.7.tar.gz", hash = "sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"},
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
]
decorator = [
{file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
@ -652,6 +885,10 @@ importlib-metadata = [
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
ipykernel = [
{file = "ipykernel-5.3.2-py3-none-any.whl", hash = "sha256:0a5f1fc6f63241b9710b5960d314ffe44d8a18bf6674e3f28d2542b192fa318c"},
{file = "ipykernel-5.3.2.tar.gz", hash = "sha256:89dc4bd19c7781f6d7eef0e666c59ce57beac56bb39b511544a71397b7b31cbb"},
@ -727,6 +964,26 @@ mistune = [
{file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
]
mypy = [
{file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"},
{file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"},
{file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"},
{file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"},
{file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"},
{file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"},
{file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"},
{file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"},
{file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"},
{file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"},
{file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"},
{file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"},
{file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"},
{file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
nbconvert = [
{file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"},
{file = "nbconvert-5.6.1.tar.gz", hash = "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523"},
@ -750,6 +1007,10 @@ parso = [
{file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"},
{file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"},
]
pathspec = [
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
]
pexpect = [
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
@ -758,6 +1019,10 @@ pickleshare = [
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
prometheus-client = [
{file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"},
{file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"},
@ -770,6 +1035,10 @@ ptyprocess = [
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
{file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
]
py = [
{file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
{file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
]
pygments = [
{file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"},
{file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"},
@ -781,6 +1050,14 @@ pyparsing = [
pyrsistent = [
{file = "pyrsistent-0.16.0.tar.gz", hash = "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"},
]
pytest = [
{file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"},
{file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"},
]
pytest-cov = [
{file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"},
{file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"},
]
python-dateutil = [
{file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
{file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
@ -841,6 +1118,49 @@ pyzmq = [
{file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"},
{file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"},
]
regex = [
{file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"},
{file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"},
{file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"},
{file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"},
{file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"},
{file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"},
{file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"},
{file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"},
{file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"},
{file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"},
{file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"},
{file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"},
{file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"},
{file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"},
{file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"},
{file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"},
{file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"},
{file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"},
{file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"},
{file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"},
{file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"},
{file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"},
{file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"},
{file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"},
{file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"},
{file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"},
{file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"},
{file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
{file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
]
send2trash = [
{file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
{file = "Send2Trash-1.5.0.tar.gz", hash = "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2"},
@ -857,6 +1177,10 @@ testpath = [
{file = "testpath-0.4.4-py2.py3-none-any.whl", hash = "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"},
{file = "testpath-0.4.4.tar.gz", hash = "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tornado = [
{file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"},
{file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"},
@ -872,6 +1196,38 @@ traitlets = [
{file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
{file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
]
typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"},
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"},
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
{file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"},
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"},
{file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"},
{file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
typing-extensions = [
{file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},

View File

@ -2,7 +2,7 @@
name = "rich"
homepage = "https://github.com/willmcgugan/rich"
documentation = "https://rich.readthedocs.io/en/latest/"
version = "9.2.0"
version = "9.3.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
license = "MIT"
@ -25,7 +25,7 @@ include = ["rich/py.typed"]
[tool.poetry.dependencies]
python = "^3.6"
typing-extensions = "^3.7.4"
dataclasses = {version="^0.7", python = "~3.6"}
dataclasses = {version=">=0.7,<0.9", python = "~3.6"}
pygments = "^2.6.0"
commonmark = "^0.9.0"
colorama = "^0.4.0"
@ -36,6 +36,10 @@ ipywidgets = {version = "^7.5.1", optional = true}
jupyter = ["ipywidgets"]
[tool.poetry.dev-dependencies]
pytest = "^6.1.2"
black = "^20.8b1"
mypy = "^0.790"
pytest-cov = "^2.10.1"
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@ -1,5 +0,0 @@
black==20.8b1
mypy==0.790
poetry==1.1.4
pytest==6.1.2
pytest-cov==2.10.1

View File

@ -1,11 +1,16 @@
import colorsys
import io
from time import process_time
from rich.color import Color
from rich.columns import Columns
from rich.console import Console, ConsoleOptions, RenderGroup, RenderResult
from rich.markdown import Markdown
from rich.measure import Measurement
from rich.padding import Padding
from rich.panel import Panel
from rich.pretty import Pretty
from rich.segment import Segment
from rich.style import Style
from rich.table import Table
from rich.syntax import Syntax
@ -14,28 +19,29 @@ from rich import box
class ColorBox:
def __init__(self, start: int = 16):
self.start = start
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
start = self.start
for color_start in range(start, start + 36, 6):
text = Text()
for color_no in range(color_start, color_start + 6):
text.append(" ", Style(bgcolor=f"color({color_no})"))
yield text
for y in range(0, 5):
for x in range(options.max_width):
h = x / options.max_width
l = 0.1 + ((y / 5) * 0.7)
r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0)
bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255)
color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255)
yield Segment("", Style(color=color, bgcolor=bgcolor))
yield Segment.line()
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
return Measurement(12, 12)
return Measurement(1, max_width)
def make_test_card() -> Table:
"""Get a renderable that demonstrates a number of features."""
table = Table.grid(padding=1, pad_edge=True)
table.title = "Rich features"
table.add_column("Feature", no_wrap=True, justify="right", style="bold red")
table.add_column("Feature", no_wrap=True, justify="center", style="bold red")
table.add_column("Demonstration")
color_table = Table(
@ -44,25 +50,27 @@ def make_test_card() -> Table:
show_header=False,
show_edge=False,
pad_edge=False,
padding=0,
)
color_table.add_row(*(ColorBox(16 + color * 36) for color in range(6)))
table.add_row(
"Colors",
RenderGroup(
"[bold yellow]256[/] colors or [bold green]16.7 million[/] colors [blue](if supported by your terminal)[/].",
Padding(color_table, (1, 0, 0, 0)),
color_table.add_row(
# "[bold yellow]256[/] colors or [bold green]16.7 million[/] colors [blue](if supported by your terminal)[/].",
(
"✓ [bold green]4-bit color[/]\n"
"✓ [bold blue]8-bit color[/]\n"
"✓ [bold magenta]Truecolor (16.7 million)[/]\n"
"✓ [bold yellow]Dumb terminals[/]\n"
"✓ [bold cyan]Automatic color conversion"
),
ColorBox(),
)
table.add_row("Colors", color_table)
table.add_row(
"Styles",
"All ansi styles: [bold]bold[/], [dim]dim[/], [italic]italic[/italic], [underline]underline[/], [strike]strikethrough[/], [reverse]reverse[/], and even [blink]blink[/].",
)
lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus. Donec faucibus vel arcu id pretium."
lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus."
lorem_table = Table.grid(padding=1, collapse_padding=True)
lorem_table.pad_edge = False
lorem_table.add_row(
@ -89,33 +97,37 @@ def make_test_card() -> Table:
return table
table.add_row(
"CJK support",
Panel(
"该库支持中文,日文和韩文文本!",
expand=False,
border_style="red",
box=box.DOUBLE_EDGE,
),
"Asian\nlanguage\nsupport",
":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다",
)
emoji_example = (
"Render emoji code: :+1: :apple: :ant: :bear: :baguette_bread: :bus: "
)
table.add_row("Emoji", comparison(Text(emoji_example), emoji_example))
markup_example = "[bold magenta]Rich[/] supports a simple [i]bbcode[/i] like [b]markup[/b], you can use to insert [yellow]color[/] and [underline]style[/]."
table.add_row(
"Console markup",
comparison(Text(markup_example), markup_example),
markup_example = (
"[bold magenta]Rich[/] supports a simple [i]bbcode[/i] like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! "
":+1: :apple: :ant: :bear: :baguette_bread: :bus: "
)
table.add_row("Markup", markup_example)
example_table = Table(
title="Star Wars box office", show_header=True, header_style="bold magenta"
show_edge=False,
show_header=True,
expand=False,
row_styles=["none", "dim"],
box=box.SIMPLE,
)
example_table.add_column("[green]Date", style="green", no_wrap=True)
example_table.add_column("[blue]Title", style="blue")
example_table.add_column(
"[cyan]Production Budget",
style="cyan",
justify="right",
no_wrap=True,
)
example_table.add_column(
"[magenta]Box Office",
style="magenta",
justify="right",
no_wrap=True,
)
example_table.add_column("Date", style="dim", no_wrap=True)
example_table.add_column("Title")
example_table.add_column("Production Budget", justify="right", no_wrap=True)
example_table.add_column("Box Office", justify="right", no_wrap=True)
example_table.add_row(
"Dec 20, 2019",
"Star Wars: The Rise of Skywalker",
@ -124,7 +136,7 @@ def make_test_card() -> Table:
)
example_table.add_row(
"May 25, 2018",
"[red]Solo[/red]: A Star Wars Story",
"[b]Solo[/]: A Star Wars Story",
"$275,000,000",
"$393,151,347",
)
@ -134,6 +146,12 @@ def make_test_card() -> Table:
"$262,000,000",
"[bold]$1,332,539,889[/bold]",
)
example_table.add_row(
"May 19, 1999",
"Star Wars Ep. [b]I[/b]: [i]The phantom Menace",
"$115,000,000",
"$1,027,044,677",
)
table.add_row("Tables", example_table)
@ -150,7 +168,24 @@ def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
previous_value = value
yield True, previous_value'''
table.add_row("Syntax highlighting", Syntax(code, "python3", line_numbers=True))
pretty_data = {
"foo": [
3.1427,
(
"Paul Atriedies",
"Vladimir Harkonnen",
"Thufir Haway",
),
],
"atomic": (False, True, None),
}
table.add_row(
"Syntax\nhighlighting\n&\npretty\nprinting",
comparison(
Syntax(code, "python3", line_numbers=True, indent_guides=True),
Pretty(pretty_data, indent_guides=True),
),
)
markdown_example = """\
# Markdown
@ -162,10 +197,13 @@ Supports much of the *markdown*, __syntax__!
- Block quotes
- Lists, and more...
"""
table.add_row("Markdown", comparison(markdown_example, Markdown(markdown_example)))
table.add_row(
"Markdown", comparison("[cyan]" + markdown_example, Markdown(markdown_example))
)
table.add_row(
"And more", """Progress bars, styled logging handler, tracebacks, etc..."""
"+more!",
"""Progress bars, columns, styled logging handler, tracebacks, etc...""",
)
return table

View File

@ -16,11 +16,13 @@ class LogRender:
show_level: bool = False,
show_path: bool = True,
time_format: str = "[%x %X]",
level_width: Optional[int] = 8,
) -> None:
self.show_time = show_time
self.show_level = show_level
self.show_path = show_path
self.time_format = time_format
self.level_width = level_width
self._last_time: Optional[str] = None
def __call__(
@ -42,14 +44,13 @@ class LogRender:
if self.show_time:
output.add_column(style="log.time")
if self.show_level:
output.add_column(style="log.level", width=8)
output.add_column(style="log.level", width=self.level_width)
output.add_column(ratio=1, style="log.message", overflow="fold")
if self.show_path and path:
output.add_column(style="log.path")
row: List["RenderableType"] = []
if self.show_time:
if log_time is None:
log_time = datetime.now()
log_time = log_time or console.get_datetime()
log_time_display = log_time.strftime(time_format or self.time_format)
if log_time_display == self._last_time:
row.append(Text(" " * len(log_time_display)))
@ -71,3 +72,11 @@ class LogRender:
output.add_row(*row)
return output
if __name__ == "__main__": # pragma: no cover
from rich.console import Console
c = Console()
c.print("[on blue]Hello", justify="right")
c.log("[on blue]hello", justify="right")

33
rich/abc.py Normal file
View File

@ -0,0 +1,33 @@
from abc import ABC
class RichRenderable(ABC):
"""An abstract base class for Rich renderables.
Note that there is no need to extend this class, the intended use is to check if an
object supports the Rich renderable protocol. For example::
if isinstance(my_object, RichRenderable):
console.print(my_object)
"""
@classmethod
def __subclasshook__(cls, other: type) -> bool:
"""Check if this class supports the rich render protocol."""
return hasattr(other, "__rich_console__") or hasattr(other, "__rich__")
if __name__ == "__main__": # pragma: no cover
from rich.text import Text
t = Text()
print(isinstance(Text, RichRenderable))
print(isinstance(t, RichRenderable))
class Foo:
pass
f = Foo()
print(isinstance(f, RichRenderable))
print(isinstance("", RichRenderable))

229
rich/ansi.py Normal file
View File

@ -0,0 +1,229 @@
from contextlib import suppress
import re
from typing import Iterable, NamedTuple
from .color import Color
from .style import Style
from .text import Text
re_ansi = re.compile(r"(?:\x1b\[(.*?)m)|(?:\x1b\](.*?)\x1b\\)")
re_csi = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
class _AnsiToken(NamedTuple):
"""Result of ansi tokenized string."""
plain: str = ""
sgr: str = ""
osc: str = ""
def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]:
"""Tokenize a string in to plain text and ANSI codes.
Args:
ansi_text (str): A String containing ANSI codes.
Yields:
AnsiToken: A named tuple of (plain, sgr, osc)
"""
def remove_csi(ansi_text: str) -> str:
"""Remove unknown CSI sequences."""
return re_csi.sub("", ansi_text)
position = 0
for match in re_ansi.finditer(ansi_text):
start, end = match.span(0)
sgr, osc = match.groups()
if start > position:
yield _AnsiToken(remove_csi(ansi_text[position:start]))
yield _AnsiToken("", sgr, osc)
position = end
if position < len(ansi_text):
yield _AnsiToken(remove_csi(ansi_text[position:]))
SGR_STYLE_MAP = {
1: "bold",
2: "dim",
3: "italic",
4: "underline",
5: "blink",
6: "blink2",
7: "reverse",
8: "conceal",
9: "strike",
21: "underline2",
22: "not dim not bold",
23: "not italic",
24: "not underline",
25: "not blink",
26: "not blink2",
27: "not reverse",
28: "not conceal",
29: "not strike",
30: "color(0)",
31: "color(1)",
32: "color(2)",
33: "color(3)",
34: "color(4)",
35: "color(5)",
36: "color(6)",
37: "color(7)",
39: "default",
40: "on color(0)",
41: "on color(1)",
42: "on color(2)",
43: "on color(3)",
44: "on color(4)",
45: "on color(5)",
46: "on color(6)",
47: "on color(7)",
49: "on default",
51: "frame",
52: "encircle",
53: "overline",
54: "not frame not encircle",
55: "not overline",
90: "color(8)",
91: "color(9)",
92: "color(10)",
93: "color(11)",
94: "color(12)",
95: "color(13)",
96: "color(14)",
97: "color(15)",
100: "on color(8)",
101: "on color(9)",
102: "on color(10)",
103: "on color(11)",
104: "on color(12)",
105: "on color(13)",
106: "on color(14)",
107: "on color(15)",
}
class AnsiDecoder:
"""Translate ANSI code in to styled Text."""
def __init__(self) -> None:
self.style = Style.null()
def decode(self, terminal_text: str) -> Iterable[Text]:
"""Decode ANSI codes in an interable of lines.
Args:
lines (Iterable[str]): An iterable of lines of terminal output.
Yields:
Text: Marked up Text.
"""
for line in terminal_text.splitlines():
yield self.decode_line(line)
def decode_line(self, line: str) -> Text:
"""Decode a line containing ansi codes.
Args:
line (str): A line of terminal output.
Returns:
Text: A Text instance marked up according to ansi codes.
"""
from_ansi = Color.from_ansi
from_rgb = Color.from_rgb
_Style = Style
text = Text()
append = text.append
line = line.rsplit("\r", 1)[-1]
for token in _ansi_tokenize(line):
plain_text, sgr, osc = token
if plain_text:
append(plain_text, self.style or None)
elif osc:
if osc.startswith("8;"):
_params, semicolon, link = osc[2:].partition(";")
if semicolon:
self.style = self.style.update_link(link or None)
elif sgr:
# Translate in to semi-colon separated codes
# Ignore invalid codes, because we want to be lenient
codes = [
min(255, int(_code)) for _code in sgr.split(";") if _code.isdigit()
]
iter_codes = iter(codes)
for code in iter_codes:
if code == 0:
# reset
self.style = _Style.null()
elif code in SGR_STYLE_MAP:
# styles
self.style += _Style.parse(SGR_STYLE_MAP[code])
elif code == 38:
#  Foreground
with suppress(StopIteration):
color_type = next(iter_codes)
if color_type == 5:
self.style += _Style.from_color(
from_ansi(next(iter_codes))
)
elif color_type == 2:
self.style += _Style.from_color(
from_rgb(
next(iter_codes),
next(iter_codes),
next(iter_codes),
)
)
elif code == 48:
# Background
with suppress(StopIteration):
color_type = next(iter_codes)
if color_type == 5:
self.style += _Style.from_color(
None, from_ansi(next(iter_codes))
)
elif color_type == 2:
self.style += _Style.from_color(
None,
from_rgb(
next(iter_codes),
next(iter_codes),
next(iter_codes),
),
)
return text
if __name__ == "__main__": # pragma: no cover
import pty
import io
import os
import sys
decoder = AnsiDecoder()
stdout = io.BytesIO()
def read(fd):
data = os.read(fd, 1024)
stdout.write(data)
return data
pty.spawn(sys.argv[1:], read)
from .console import Console
console = Console(record=True)
stdout_result = stdout.getvalue().decode("utf-8")
print(stdout_result)
for line in decoder.decode(stdout_result):
console.print(line)
console.save_html("stdout.html")

View File

@ -1,4 +1,4 @@
from typing import Optional, Union
from typing import Union
from .color import Color
from .console import Console, ConsoleOptions, RenderResult

View File

@ -327,6 +327,22 @@ class Color(NamedTuple):
assert self.number is None
return theme.foreground_color if foreground else theme.background_color
@classmethod
def from_ansi(cls, number: int) -> "Color":
"""Create a Color number from it's 8-bit ansi number.
Args:
number (int): A number between 0-255 inclusive.
Returns:
Color: A new Color instance.
"""
return cls(
name=f"color({number})",
type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT),
number=number,
)
@classmethod
def from_triplet(cls, triplet: "ColorTriplet") -> "Color":
"""Create a truecolor RGB color from a triplet of values.

View File

@ -7,8 +7,10 @@ import threading
from abc import ABC, abstractmethod
from collections import abc
from dataclasses import dataclass, field, replace
from datetime import datetime
from functools import wraps
from getpass import getpass
from time import monotonic
from typing import (
IO,
TYPE_CHECKING,
@ -401,6 +403,9 @@ class Console:
highlighter (HighlighterType, optional): Default highlighter.
legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``.
safe_box (bool, optional): Restrict box options that don't render on legacy Windows.
get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log),
or None for datetime.now.
get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic.
"""
def __init__(
@ -426,6 +431,8 @@ class Console:
highlighter: Optional["HighlighterType"] = ReprHighlighter(),
legacy_windows: bool = None,
safe_box: bool = True,
get_datetime: Callable[[], datetime] = None,
get_time: Callable[[], float] = None,
_environ: Dict[str, str] = None,
):
# Copy of os.environ allows us to replace it for testing
@ -467,6 +474,8 @@ class Console:
)
self.highlighter: HighlighterType = highlighter or _null_highlighter
self.safe_box = safe_box
self.get_datetime = get_datetime or datetime.now
self.get_time = get_time or monotonic
self._record_buffer_lock = threading.RLock()
self._thread_locals = ConsoleThreadLocals(
@ -709,8 +718,8 @@ class Console:
def pager(
self, pager: Pager = None, styles: bool = False, links: bool = False
) -> PagerContext:
"""A context manager to display anything printed within a "pager". The pager used
is defined by the system and will typically support at less pressing a key to scroll.
"""A context manager to display anything printed within a "pager". The pager application
is defined by the system and will typically support at least pressing a key to scroll.
Args:
pager (Pager, optional): A pager object, or None to use :class:~rich.pager.SystemPager`. Defaults to None.
@ -958,7 +967,7 @@ class Console:
def check_text() -> None:
if text:
sep_text = Text(sep, end=end)
sep_text = Text(sep, justify=justify, end=end)
append(sep_text.join(text))
del text[:]
@ -1025,14 +1034,15 @@ class Console:
highlight: bool = True,
) -> None:
"""Output to the terminal. This is a low-level way of writing to the terminal which unlike
:meth:`~rich.console.Console.print` doesn't pretty print, wrap text, nor markup, but will highlighting
and apply basic style.
:meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will
optionally apply highlighting and a basic style.
Args:
sep (str, optional): String to write between print data. Defaults to " ".
end (str, optional): String to write at end of print data. Defaults to "\\n".
style (Union[str, Style], optional): A style to apply to output. Defaults to None.
highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``.
highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use
console default. Defaults to ``None``.
"""
raw_output: str = sep.join(str(_object) for _object in objects)
self.print(
@ -1218,6 +1228,7 @@ class Console:
self._log_render(
self,
renderables,
log_time=self.get_datetime(),
path=path,
line_no=line_no,
link_path=link_path,
@ -1274,7 +1285,7 @@ class Console:
self._record_buffer.extend(buffer)
not_terminal = not self.is_terminal
for text, style, is_control in buffer:
if style and not is_control:
if style:
append(
style.render(
text,

View File

@ -8,8 +8,6 @@ from typing import (
TYPE_CHECKING,
)
from .style import Style
if TYPE_CHECKING:
from .console import (
Console,

View File

@ -1,7 +1,6 @@
from typing import Iterable, List, TYPE_CHECKING
# from .console import Console as BaseConsole
from .__init__ import get_console
from . import get_console
from .segment import Segment
from .terminal_theme import DEFAULT_TERMINAL_THEME

View File

@ -55,8 +55,8 @@ class LiveRender:
) -> RenderResult:
style = console.get_style(self.style)
lines = console.render_lines(self.renderable, options, style=style, pad=False)
shape = Segment.get_shape(lines)
_Segment = Segment
shape = _Segment.get_shape(lines)
if self._shape is None:
self._shape = shape
else:
@ -68,8 +68,8 @@ class LiveRender:
)
width, height = self._shape
lines = Segment.set_shape(lines, width, height)
lines = _Segment.set_shape(lines, width, height)
for last, line in loop_last(lines):
yield from line
yield from _Segment.make_control(line)
if not last:
yield Segment.line()
yield _Segment.line(is_control=True)

View File

@ -36,6 +36,9 @@ class RichHandler(Handler):
tracebacks_theme (str, optional): Override pygments theme used in traceback.
tracebacks_word_wrap (bool, optional): Enable word wrapping of long tracebacks lines. Defaults to False.
tracebacks_show_locals (bool, optional): Enable display of locals in tracebacks. Defaults to False.
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
"""
KEYWORDS: ClassVar[Optional[List[str]]] = [
@ -67,12 +70,17 @@ class RichHandler(Handler):
tracebacks_theme: Optional[str] = None,
tracebacks_word_wrap: bool = True,
tracebacks_show_locals: bool = False,
locals_max_length: int = 10,
locals_max_string: int = 80,
) -> None:
super().__init__(level=level)
self.console = console or get_console()
self.highlighter = highlighter or self.HIGHLIGHTER_CLASS()
self._log_render = LogRender(
show_time=show_time, show_level=show_level, show_path=show_path
show_time=show_time,
show_level=show_level,
show_path=show_path,
level_width=None,
)
self.enable_link_path = enable_link_path
self.markup = markup
@ -82,18 +90,32 @@ class RichHandler(Handler):
self.tracebacks_theme = tracebacks_theme
self.tracebacks_word_wrap = tracebacks_word_wrap
self.tracebacks_show_locals = tracebacks_show_locals
self.locals_max_length = locals_max_length
self.locals_max_string = locals_max_string
def get_level_text(self, record: LogRecord) -> Text:
"""Get the level name from the record.
Args:
record (LogRecord): LogRecord instance.
Returns:
Text: A tuple of the style and level name.
"""
level_name = record.levelname
level_text = Text.styled(
level_name.ljust(8), f"logging.level.{level_name.lower()}"
)
return level_text
def emit(self, record: LogRecord) -> None:
"""Invoked by logging."""
path = Path(record.pathname).name
log_style = f"logging.level.{record.levelname.lower()}"
level = self.get_level_text(record)
message = self.format(record)
time_format = None if self.formatter is None else self.formatter.datefmt
log_time = datetime.fromtimestamp(record.created)
level = Text()
level.append(record.levelname, log_style)
traceback = None
if (
self.rich_tracebacks
@ -112,6 +134,8 @@ class RichHandler(Handler):
theme=self.tracebacks_theme,
word_wrap=self.tracebacks_word_wrap,
show_locals=self.tracebacks_show_locals,
locals_max_length=self.locals_max_length,
locals_max_string=self.locals_max_string,
)
message = record.getMessage()
@ -186,6 +210,8 @@ if __name__ == "__main__": # pragma: no cover
def divide():
number = 1
divisor = 0
foos = ["foo"] * 100
log.debug("in divide")
try:
number / divisor
except:

View File

@ -158,11 +158,17 @@ class Panel(JupyterMixin):
_, right, _, left = Padding.unpack(self.padding)
padding = left + right
renderables = [self.renderable, _title] if _title else [self.renderable]
width = (
measure_renderables(console, renderables, max_width - padding - 2).maximum
+ padding
+ 2
)
if self.width is None:
width = (
measure_renderables(
console, renderables, max_width - padding - 2
).maximum
+ padding
+ 2
)
else:
width = self.width
return Measurement(width, width)

View File

@ -19,7 +19,8 @@ from typing import (
from rich.highlighter import ReprHighlighter
from .__init__ import get_console
from .abc import RichRenderable
from . import get_console
from ._pick import pick_bool
from .cells import cell_len
from .highlighter import ReprHighlighter
@ -43,6 +44,8 @@ def install(
crop: bool = False,
indent_guides: bool = False,
max_length: int = None,
max_string: int = None,
expand_all: bool = False,
) -> None:
"""Install automatic pretty printing in the Python REPL.
@ -53,6 +56,8 @@ def install(
indent_guides (bool, optional): Enable indentation guides. Defaults to False.
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
expand_all (bool, optional): Expand all containers. Defaults to False
"""
from rich import get_console
@ -65,12 +70,14 @@ def install(
builtins._ = None # type: ignore
console.print(
value
if hasattr(value, "__rich_console__") or hasattr(value, "__rich__")
if isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
indent_guides=indent_guides,
max_length=max_length,
max_string=max_string,
expand_all=expand_all,
),
crop=crop,
)
@ -93,6 +100,7 @@ class Pretty:
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
expand_all (bool, optional): Expand all containers. Defaults to False.
"""
def __init__(
@ -107,6 +115,7 @@ class Pretty:
indent_guides: bool = False,
max_length: int = None,
max_string: int = None,
expand_all: bool = False,
) -> None:
self._object = _object
self.highlighter = highlighter or ReprHighlighter()
@ -117,6 +126,7 @@ class Pretty:
self.indent_guides = indent_guides
self.max_length = max_length
self.max_string = max_string
self.expand_all = expand_all
def __rich_console__(
self, console: "Console", options: "ConsoleOptions"
@ -127,6 +137,7 @@ class Pretty:
indent_size=self.indent_size,
max_length=self.max_length,
max_string=self.max_string,
expand_all=self.expand_all,
)
pretty_text = Text(
pretty_str,
@ -144,7 +155,11 @@ class Pretty:
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
pretty_str = pretty_repr(
self._object, max_width=max_width, indent_size=self.indent_size
self._object,
max_width=max_width,
indent_size=self.indent_size,
max_length=self.max_length,
max_string=self.max_string,
)
text_width = max(cell_len(line) for line in pretty_str.splitlines())
return Measurement(text_width, text_width)
@ -191,6 +206,11 @@ class Node:
is_tuple: bool = False
children: Optional[List["Node"]] = None
@property
def separator(self) -> str:
"""Get separator between items."""
return "" if self.last else ","
def iter_tokens(self) -> Iterable[str]:
"""Generate tokens for this node."""
if self.key_repr:
@ -247,7 +267,7 @@ class Node:
Returns:
str: A repr string of the original object.
"""
lines = [_Line(node=self)]
lines = [_Line(node=self, is_root=True)]
line_no = 0
while line_no < len(lines):
line = lines[line_no]
@ -264,6 +284,7 @@ class Node:
class _Line:
"""A line in repr output."""
is_root: bool = False
node: Optional[Node] = None
text: str = ""
suffix: str = ""
@ -296,18 +317,20 @@ class _Line:
else:
yield _Line(text=node.open_brace, whitespace=whitespace)
child_whitespace = self.whitespace + " " * indent_size
tuple_of_one = node.is_tuple and len(node.children) == 1
for child in node.children:
separator = "," if tuple_of_one else child.separator
line = _Line(
node=child,
whitespace=child_whitespace,
suffix="" if child.last else ",",
suffix=separator,
)
yield line
yield _Line(
text=node.close_brace,
whitespace=whitespace,
suffix="" if node.last else ",",
suffix="," if (tuple_of_one and not self.is_root) else node.separator,
)
def __str__(self) -> str:
@ -409,9 +432,9 @@ def pretty_repr(
*,
max_width: int = 80,
indent_size: int = 4,
expand_all: bool = False,
max_length: int = None,
max_string: int = None,
expand_all: bool = False,
) -> str:
"""Prettify repr string by expanding on to new lines to fit within a given width.
@ -419,11 +442,11 @@ def pretty_repr(
_object (Any): Object to repr.
max_width (int, optional): Desired maximum width of repr string. Defaults to 80.
indent_size (int, optional): Number of spaces to indent. Defaults to 4.
expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable truncating.
Defaults to None.
expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False.
Returns:
str: A possibly multi-line representation of the object.
@ -443,9 +466,10 @@ def pprint(
_object: Any,
*,
console: "Console" = None,
indent_guides: bool = True,
max_length: int = None,
max_string: int = None,
indent_guides: bool = True,
expand_all: bool = False,
):
"""A convenience function for pretty printing.
@ -456,6 +480,7 @@ def pprint(
Defaults to None.
max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None.
indent_guides (bool, optional): Enable indentation guides. Defaults to True.
expand_all (bool, optional): Expand all containers. Defaults to False.
"""
_console = get_console() if console is None else console
_console.print(
@ -464,7 +489,10 @@ def pprint(
max_length=max_length,
max_string=max_string,
indent_guides=indent_guides,
)
expand_all=expand_all,
overflow="ignore",
),
soft_wrap=True,
)

View File

@ -7,10 +7,8 @@ from dataclasses import dataclass, field
from datetime import timedelta
from math import ceil
from threading import Event, RLock, Thread
from time import monotonic
from typing import (
IO,
TYPE_CHECKING,
Any,
Callable,
Deque,
@ -27,7 +25,7 @@ from typing import (
)
from . import filesize, get_console
from .progress_bar import ProgressBar
from .ansi import AnsiDecoder
from .console import (
Console,
ConsoleRenderable,
@ -40,6 +38,7 @@ from .control import Control
from .highlighter import Highlighter
from .jupyter import JupyterMixin
from .live_render import LiveRender
from .progress_bar import ProgressBar
from .style import StyleType
from .table import Table
from .text import Text
@ -480,6 +479,7 @@ class _FileProxy(io.TextIOBase):
self.__console = console
self.__file = file
self.__buffer: List[str] = []
self.__ansi_decoder = AnsiDecoder()
def __getattr__(self, name: str) -> Any:
return getattr(self.__file, name)
@ -498,7 +498,9 @@ class _FileProxy(io.TextIOBase):
if lines:
console = self.__console
with console:
output = "\n".join(lines)
output = Text("\n").join(
self.__ansi_decoder.decode_line(line) for line in lines
)
console.print(output, markup=False, emoji=False, highlight=False)
return len(text)
@ -520,7 +522,8 @@ class Progress(JupyterMixin, RenderHook):
transient: (bool, optional): Clear the progress on exit. Defaults to False.
redirect_stdout: (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True.
redirect_stderr: (bool, optional): Enable redirection of stderr. Defaults to True.
get_time: (Callable, optional): A callable that gets the current time, or None to use time.monotonic. Defaults to None.
get_time: (Callable, optional): A callable that gets the current time, or None to use Console.get_time. Defaults to None.
disable (bool, optional): Disable progress display. Defaults to False
"""
def __init__(
@ -534,6 +537,7 @@ class Progress(JupyterMixin, RenderHook):
redirect_stdout: bool = True,
redirect_stderr: bool = True,
get_time: GetTimeCallable = None,
disable: bool = False,
) -> None:
assert (
refresh_per_second is None or refresh_per_second > 0
@ -552,7 +556,8 @@ class Progress(JupyterMixin, RenderHook):
self.transient = transient
self._redirect_stdout = redirect_stdout
self._redirect_stderr = redirect_stderr
self.get_time = get_time or monotonic
self.get_time = get_time or self.console.get_time
self.disable = disable
self._tasks: Dict[TaskID, Task] = {}
self._live_render = LiveRender(self.get_renderable())
self._task_index: TaskID = TaskID(0)
@ -592,7 +597,7 @@ class Progress(JupyterMixin, RenderHook):
sys.stdout = _FileProxy(self.console, sys.stdout)
if self._redirect_stderr:
self._restore_stderr = sys.stderr
sys.stdout = _FileProxy(self.console, sys.stdout)
sys.stderr = _FileProxy(self.console, sys.stderr)
def _disable_redirect_io(self):
"""Disable redirecting of stdout / stderr."""
@ -846,8 +851,8 @@ class Progress(JupyterMixin, RenderHook):
"""Refresh (render) the progress information."""
if self.console.is_jupyter: # pragma: no cover
try:
from ipywidgets import Output
from IPython.display import display
from ipywidgets import Output
except ImportError:
import warnings
@ -862,7 +867,11 @@ class Progress(JupyterMixin, RenderHook):
self.ipy_widget.clear_output(wait=True)
self.console.print(self.get_renderable())
elif self.console.is_terminal and not self.console.is_dumb_terminal:
elif (
self.console.is_terminal
and not self.console.is_dumb_terminal
and not self.disable
):
with self._lock:
self._live_render.set_renderable(self.get_renderable())
with self.console:
@ -974,13 +983,13 @@ class Progress(JupyterMixin, RenderHook):
if __name__ == "__main__": # pragma: no coverage
import time
import random
import time
from .panel import Panel
from .rule import Rule
from .syntax import Syntax
from .table import Table
from .rule import Rule
syntax = Syntax(
'''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
@ -1017,16 +1026,20 @@ if __name__ == "__main__": # pragma: no coverage
examples = cycle(progress_renderables)
console = Console()
with Progress(console=console, transient=True) as progress:
console = Console(record=True)
try:
with Progress(console=console, transient=True) as progress:
task1 = progress.add_task("[red]Downloading", total=1000)
task2 = progress.add_task("[green]Processing", total=1000)
task3 = progress.add_task("[yellow]Thinking", total=1000, start=False)
task1 = progress.add_task("[red]Downloading", total=1000)
task2 = progress.add_task("[green]Processing", total=1000)
task3 = progress.add_task("[yellow]Thinking", total=1000, start=False)
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
time.sleep(0.01)
if random.randint(0, 100) < 1:
progress.log(next(examples))
while not progress.finished:
progress.update(task1, advance=0.5)
progress.update(task2, advance=0.3)
time.sleep(0.01)
if random.randint(0, 100) < 1:
progress.log(next(examples))
except:
console.save_html("progress.html")
print("wrote progress.html")

View File

@ -1,6 +1,6 @@
from typing import IO, Any, Generic, List, Optional, TextIO, TypeVar, Union, overload
from typing import Any, Generic, List, Optional, TextIO, TypeVar, Union, overload
from .__init__ import get_console
from . import get_console
from .console import Console
from .text import Text, TextType

View File

@ -1,10 +1,8 @@
from typing import Any
from .abc import RichRenderable
def is_renderable(test_renderable: Any) -> bool:
def is_renderable(check_object: Any) -> bool:
"""Check if an object may be rendered by Rich."""
return (
isinstance(test_renderable, str)
or hasattr(test_renderable, "__rich_console__")
or hasattr(test_renderable, "__rich__")
)
return isinstance(check_object, str) or isinstance(check_object, RichRenderable)

View File

@ -1,4 +1,4 @@
from typing import Optional, Union
from typing import Union
from .cells import cell_len, set_cell_size
from .console import Console, ConsoleOptions, RenderResult

View File

@ -1,13 +1,11 @@
from typing import Any, Tuple, TYPE_CHECKING
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Tuple
from .highlighter import ReprHighlighter
from .panel import Panel
from .pretty import Pretty
from .text import Text, TextType
from .table import Table
from .text import Text, TextType
if TYPE_CHECKING:
from .console import ConsoleRenderable, RenderableType
@ -18,7 +16,9 @@ def render_scope(
*,
title: TextType = None,
sort_keys: bool = True,
indent_guides: bool = False
indent_guides: bool = False,
max_length: int = None,
max_string: int = None,
) -> "ConsoleRenderable":
"""Render python variables in a given scope.
@ -27,6 +27,9 @@ def render_scope(
title (str, optional): Optional title. Defaults to None.
sort_keys (bool, optional): Enable sorting of items. Defaults to True.
indent_guides (bool, optional): Enable indentaton guides. Defaults to False.
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to None.
max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None.
Returns:
RenderableType: A renderable object.
@ -48,7 +51,13 @@ def render_scope(
)
items_table.add_row(
key_text,
Pretty(value, highlighter=highlighter, indent_guides=indent_guides),
Pretty(
value,
highlighter=highlighter,
indent_guides=indent_guides,
max_length=max_length,
max_string=max_string,
),
)
return Panel.fit(
items_table,

View File

@ -41,21 +41,31 @@ class Segment(NamedTuple):
return 0 if self.is_control else cell_len(self.text)
@classmethod
def control(cls, text: str) -> "Segment":
def control(cls, text: str, style: Optional[Style] = None) -> "Segment":
"""Create a Segment with control codes.
Args:
text (str): Text containing non-printable control codes.
style (Optional[style]): Optional style.
Returns:
Segment: A Segment instance with ``is_control=True``.
"""
return Segment(text, is_control=True)
return cls(text, style, is_control=True)
@classmethod
def line(cls) -> "Segment":
def make_control(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
"""Convert all segments in to control segments.
Returns:
Iterable[Segments]: Segments with is_control=True
"""
return [cls(text, style, True) for text, style, _ in segments]
@classmethod
def line(cls, is_control: bool = False) -> "Segment":
"""Make a new line segment."""
return Segment("\n")
return cls("\n", is_control=is_control)
@classmethod
def apply_style(
@ -206,9 +216,9 @@ class Segment(NamedTuple):
append(segment)
line_length += segment_length
else:
text, style, _ = segment
text, segment_style, _ = segment
text = set_cell_size(text, length - line_length)
append(cls(text, style))
append(cls(text, segment_style))
break
else:
new_line = line[:]
@ -278,7 +288,7 @@ class Segment(NamedTuple):
"""Simplify an iterable of segments by combining contiguous segments with the same style.
Args:
segments (Iterable[Segment]): An iterable segments.
segments (Iterable[Segment]): An iterable of segments.
Returns:
Iterable[Segment]: A possibly smaller iterable of segments that will render the same way.
@ -304,6 +314,9 @@ class Segment(NamedTuple):
def strip_links(cls, segments: Iterable["Segment"]) -> Iterable["Segment"]:
"""Remove all links from an iterable of styles.
Args:
segments (Iterable[Segment]): An iterable segments.
Yields:
Segment: Segments with link removed.
"""

View File

@ -175,6 +175,35 @@ class Style:
"""Create an 'null' style, equivalent to Style(), but more performant."""
return NULL_STYLE
@classmethod
def from_color(cls, color: Color = None, bgcolor: Color = None) -> "Style":
"""Create a new style with colors and no attributes.
Returns:
color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None.
bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None.
"""
style = cls.__new__(Style)
style._ansi = None
style._style_definition = None
style._color = color
style._bgcolor = bgcolor
style._set_attributes = 0
style._attributes = 0
style._link = None
style._link_id = ""
style._hash = hash(
(
color,
bgcolor,
None,
None,
None,
)
)
style._null = not (color or bgcolor)
return style
bold = _Bit(0)
dim = _Bit(1)
italic = _Bit(2)
@ -355,7 +384,7 @@ class Style:
return Style(bgcolor=self.bgcolor)
@classmethod
@lru_cache(maxsize=1024)
@lru_cache(maxsize=4096)
def parse(cls, style_definition: str) -> "Style":
"""Parse a style definition.
@ -369,7 +398,7 @@ class Style:
`Style`: A Style instance.
"""
if style_definition.strip() == "none":
return cls()
return cls.null()
style_attributes = {
"dim": "dim",

View File

@ -98,6 +98,7 @@ class Table(JupyterMixin):
title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`). Defaults to box.HEAVY_HEAD.
safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import
import os.path
import platform
import sys
from dataclasses import dataclass, field
@ -41,6 +42,9 @@ from .theme import Theme
WINDOWS = platform.system() == "Windows"
LOCALS_MAX_LENGTH = 10
LOCALS_MAX_STRING = 80
def install(
*,
@ -144,6 +148,9 @@ class Traceback:
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
"""
def __init__(
@ -155,6 +162,8 @@ class Traceback:
word_wrap: bool = False,
show_locals: bool = False,
indent_guides: bool = True,
locals_max_length: int = LOCALS_MAX_LENGTH,
locals_max_string: int = LOCALS_MAX_STRING,
):
if trace is None:
exc_type, exc_value, traceback = sys.exc_info()
@ -172,6 +181,8 @@ class Traceback:
self.word_wrap = word_wrap
self.show_locals = show_locals
self.indent_guides = indent_guides
self.locals_max_length = locals_max_length
self.locals_max_string = locals_max_string
@classmethod
def from_exception(
@ -185,6 +196,8 @@ class Traceback:
word_wrap: bool = False,
show_locals: bool = False,
indent_guides: bool = True,
locals_max_length: int = LOCALS_MAX_LENGTH,
locals_max_string: int = LOCALS_MAX_STRING,
) -> "Traceback":
"""Create a traceback from exception info
@ -198,6 +211,9 @@ class Traceback:
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
Returns:
Traceback: A Traceback instance that may be printed.
@ -213,6 +229,8 @@ class Traceback:
word_wrap=word_wrap,
show_locals=show_locals,
indent_guides=indent_guides,
locals_max_length=locals_max_length,
locals_max_string=locals_max_string,
)
@classmethod
@ -222,6 +240,8 @@ class Traceback:
exc_value: BaseException,
traceback: Optional[TracebackType],
show_locals: bool = False,
locals_max_length: int = LOCALS_MAX_LENGTH,
locals_max_string: int = LOCALS_MAX_STRING,
) -> Trace:
"""Extract traceback information.
@ -230,6 +250,9 @@ class Traceback:
exc_value (BaseException): Exception value.
traceback (TracebackType): Python Traceback object.
show_locals (bool, optional): Enable display of local variables. Defaults to False.
locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
Defaults to 10.
locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
Returns:
Trace: A Trace instance which you can use to construct a `Traceback`.
@ -252,12 +275,18 @@ class Traceback:
append = stack.frames.append
for frame_summary, line_no in walk_tb(traceback):
filename = frame_summary.f_code.co_filename
filename = os.path.abspath(filename) if filename else "?"
frame = Frame(
filename=frame_summary.f_code.co_filename or "?",
filename=filename,
lineno=line_no,
name=frame_summary.f_code.co_name,
locals={
key: pretty.traverse(value)
key: pretty.traverse(
value,
max_length=locals_max_length,
max_string=locals_max_string,
)
for key, value in frame_summary.f_locals.items()
}
if show_locals
@ -436,6 +465,8 @@ class Traceback:
frame.locals,
title="locals",
indent_guides=self.indent_guides,
max_length=self.locals_max_length,
max_string=self.locals_max_string,
),
],
padding=1,

File diff suppressed because one or more lines are too long

32
tests/test_ansi.py Normal file
View File

@ -0,0 +1,32 @@
import io
from rich.ansi import AnsiDecoder
from rich.console import Console
from rich.style import Style
from rich.text import Span, Text
def test_decode():
console = Console(
force_terminal=True, legacy_windows=False, color_system="truecolor"
)
console.begin_capture()
console.print("Hello")
console.print("[b]foo[/b]")
console.print("[link http://example.org]bar")
console.print("[#ff0000 on color(200)]red")
console.print("[color(200) on #ff0000]red")
terminal_codes = console.end_capture()
decoder = AnsiDecoder()
lines = list(decoder.decode(terminal_codes))
expected = [
Text("Hello"),
Text("foo", spans=[Span(0, 3, Style.parse("bold"))]),
Text("bar", spans=[Span(0, 3, Style.parse("link http://example.org"))]),
Text("red", spans=[Span(0, 3, Style.parse("#ff0000 on color(200)"))]),
Text("red", spans=[Span(0, 3, Style.parse("color(200) on #ff0000"))]),
]
assert lines == expected

View File

@ -73,6 +73,10 @@ def test_from_rgb() -> None:
)
def test_from_ansi() -> None:
assert Color.from_ansi(1) == Color("color(1)", ColorType.STANDARD, 1)
def test_default() -> None:
assert Color.default() == Color("default", ColorType.DEFAULT, None, None)

View File

@ -1,3 +1,4 @@
import datetime
import io
import os
import sys
@ -420,3 +421,11 @@ def test_render_group_fit() -> None:
min_width, _ = measure_renderables(console, renderables, 42)
assert min_width == 5
def test_get_time() -> None:
console = Console(
get_time=lambda: 99, get_datetime=lambda: datetime.datetime(1974, 7, 5)
)
assert console.get_time() == 99
assert console.get_datetime() == datetime.datetime(1974, 7, 5)

View File

@ -37,7 +37,7 @@ def test_rich_console(live_render):
encoding="utf-8",
)
rich_console = live_render.__rich_console__(Console(), options)
assert [Segment("my string", Style.parse("none"))] == list(rich_console)
assert [Segment.control("my string", Style.parse("none"))] == list(rich_console)
live_render.style = "red"
rich_console = live_render.__rich_console__(Console(), options)
assert [Segment("my string", Style.parse("red"))] == list(rich_console)
assert [Segment.control("my string", Style.parse("red"))] == list(rich_console)

View File

@ -45,6 +45,14 @@ def test_log():
assert rendered == expected
def test_justify():
console = Console(width=20, log_path=False, log_time=False, color_system=None)
console.begin_capture()
console.log("foo", justify="right")
result = console.end_capture()
assert result == " foo\n"
if __name__ == "__main__":
render = render_log()
print(render)

View File

@ -1,5 +1,6 @@
import io
from rich.console import Console
from rich.measure import Measurement
from rich.panel import Panel
import pytest
@ -42,6 +43,14 @@ def test_console_width():
assert max_width == 16
def test_fixed_width():
console = Console(file=io.StringIO(), width=50, legacy_windows=False)
panel = Panel("Hello World", width=20)
min_width, max_width = panel.__rich_measure__(console, 100)
assert min_width == 20
assert max_width == 20
if __name__ == "__main__":
expected = []
for panel in tests:

View File

@ -29,6 +29,7 @@ def test_pretty():
print(result)
print(repr(result))
expected = "{\n 'foo': [1, 2, 3, (4, 5, {6}, 7, 8, {9}), {}],\n 'bar': {\n 'egg': 'baz',\n 'words': [\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World'\n ]\n },\n False: 'foo',\n True: '',\n 'text': ('Hello World', 'foo bar baz egg')\n}"
print(expected)
assert result == expected
@ -121,3 +122,15 @@ def test_pprint_max_string():
console.begin_capture()
pprint(["Hello" * 20], console=console, max_string=8)
assert console.end_capture() == """['HelloHel'+92]\n"""
def test_tuples():
console = Console(color_system=None)
console.begin_capture()
pprint((1,), console=console)
pprint((1,), expand_all=True, console=console)
pprint(((1,),), expand_all=True, console=console)
result = console.end_capture()
print(repr(result))
expected = "(1,)\n(\n│ 1,\n)\n(\n│ (\n│ │ 1,\n│ ),\n)\n"
assert result == expected

View File

@ -1,7 +1,7 @@
# encoding=utf-8
import io
from time import time
from time import sleep
import pytest
@ -143,8 +143,10 @@ def make_progress() -> Progress:
def render_progress() -> str:
progress = make_progress()
progress.start() # superfluous noop
with progress:
pass
progress.stop() # superfluous noop
progress_render = progress.console.file.getvalue()
return progress_render
@ -328,9 +330,20 @@ def test_progress_create() -> None:
def test_refresh_thread() -> None:
progress = Progress()
thread = _RefreshThread(progress, 10)
class MockProgress:
def __init__(self):
self.count = 0
def refresh(self):
self.count += 1
progress = MockProgress()
thread = _RefreshThread(progress, 100)
assert thread.progress == progress
thread.start()
sleep(0.2)
thread.stop()
assert progress.count >= 1
def test_track_thread() -> None:
@ -372,6 +385,42 @@ def test_reset() -> None:
assert not task._progress
def test_progress_max_refresh() -> None:
"""Test max_refresh argment."""
time = 0.0
def get_time() -> float:
nonlocal time
try:
return time
finally:
time = time + 1.0
console = Console(
color_system=None, width=80, legacy_windows=False, force_terminal=True
)
column = TextColumn("{task.description}")
column.max_refresh = 3
progress = Progress(
column,
get_time=get_time,
auto_refresh=False,
console=console,
)
console.begin_capture()
with progress:
task_id = progress.add_task("start")
for tick in range(6):
progress.update(task_id, description=f"tick {tick}")
progress.refresh()
result = console.end_capture()
print(repr(result))
assert (
result
== "\x1b[?25l\r\x1b[2Kstart\r\x1b[2Kstart\r\x1b[2Ktick 1\r\x1b[2Ktick 1\r\x1b[2Ktick 3\r\x1b[2Ktick 3\r\x1b[2Ktick 5\r\x1b[2Ktick 5\n\x1b[?25h"
)
if __name__ == "__main__":
_render = render_progress()
print(_render)

View File

@ -1,12 +1,13 @@
import io
from rich.abc import RichRenderable
from rich.console import Console
from rich.panel import Panel
from rich.text import Text
class Foo:
def __rich__(self):
def __rich__(self) -> Text:
return Text("Foo")
@ -22,3 +23,13 @@ def test_rich_cast_container():
console = Console(file=io.StringIO(), legacy_windows=False)
console.print(Panel.fit(foo, padding=0))
assert console.file.getvalue() == "╭───╮\n│Foo│\n╰───╯\n"
def test_abc():
foo = Foo()
assert isinstance(foo, RichRenderable)
assert isinstance(Text("hello"), RichRenderable)
assert isinstance(Panel("hello"), RichRenderable)
assert not isinstance(foo, str)
assert not isinstance("foo", RichRenderable)
assert not isinstance([], RichRenderable)

45
tox.ini Normal file
View File

@ -0,0 +1,45 @@
[tox]
minversion = 3.9.0
envlist =
lint
docs
py{36,37,38,39}
isolated_build = True
[testenv]
description = Run unit-testing
# develop temporary disabled as project packaging does not work with it yet:
# https://github.com/willmcgugan/rich/issues/345
usedevelop = False
deps =
-r requirements-dev.txt
# do not put * in passenv as it may break builds due to reduced isolation
passenv =
CI
GITHUB_*
HOME
PYTEST_*
SSH_AUTH_SOCK
TERM
setenv =
PYTHONDONTWRITEBYTECODE=1
PYTHONUNBUFFERED=1
commands =
# failsafe as older pip may install incompatible dependencies
pip check
pytest --cov-report term-missing --cov=rich tests/ {posargs}
[testenv:lint]
description = Runs all linting tasks
commands =
black .
mypy -p rich --ignore-missing-imports --warn-unreachable
skip_install = true
[testenv:docs]
description = Builds documentation
changedir = docs
deps =
-r docs/requirements.txt
commands =
sphinx-build -M html source build