mirror of https://github.com/Textualize/rich.git
Merge branch 'master' of github.com:willmcgugan/rich into feat/add-live-table-proposal
This commit is contained in:
commit
1020405830
|
@ -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:
|
||||
|
|
35
CHANGELOG.md
35
CHANGELOG.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
alabaster==0.7.12
|
||||
Sphinx==3.2.1
|
||||
Sphinx==3.3.1
|
||||
sphinx-rtd-theme==0.5.0
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
rich.abc
|
||||
========
|
||||
|
||||
.. automodule:: rich.abc
|
||||
:members:
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
rich.pretty
|
||||
===========
|
||||
|
||||
.. automodule:: rich.pretty
|
||||
:members:
|
||||
|
|
@ -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 |
|
@ -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"},
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
black==20.8b1
|
||||
mypy==0.790
|
||||
poetry==1.1.4
|
||||
pytest==6.1.2
|
||||
pytest-cov==2.10.1
|
130
rich/__main__.py
130
rich/__main__.py
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
|
@ -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")
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional, Union
|
||||
from typing import Union
|
||||
|
||||
from .color import Color
|
||||
from .console import Console, ConsoleOptions, RenderResult
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -8,8 +8,6 @@ from typing import (
|
|||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from .style import Style
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .console import (
|
||||
Console,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue