mirror of
https://github.com/Textualize/rich.git
synced 2025-08-19 01:40:37 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
02741b795d
57 changed files with 3047 additions and 282 deletions
22
.github/workflows/pythonpackage.yml
vendored
22
.github/workflows/pythonpackage.yml
vendored
|
@ -9,7 +9,9 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
@ -17,22 +19,22 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
architecture: x64
|
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
|
- name: Install dependencies
|
||||||
run: |
|
run: poetry install
|
||||||
python -m pip install --upgrade pip
|
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||||
pip install -r requirements-dev.txt
|
|
||||||
poetry install
|
|
||||||
- name: Format check with black
|
- name: Format check with black
|
||||||
run: |
|
run: make format-check
|
||||||
make format-check
|
|
||||||
- name: Typecheck with mypy
|
- name: Typecheck with mypy
|
||||||
run: |
|
run: make typecheck
|
||||||
make typecheck
|
|
||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: |
|
run: |
|
||||||
pip install .
|
pip install .
|
||||||
python -m pytest tests -v --cov=./rich --cov-report=xml:./coverage.xml --cov-report term-missing
|
python -m pytest tests -v --cov=./rich --cov-report=xml:./coverage.xml --cov-report term-missing
|
||||||
|
|
||||||
- name: Upload code coverage
|
- name: Upload code coverage
|
||||||
uses: codecov/codecov-action@v1.0.10
|
uses: codecov/codecov-action@v1.0.10
|
||||||
with:
|
with:
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,6 +6,7 @@ mypy_report
|
||||||
docs/build
|
docs/build
|
||||||
docs/source/_build
|
docs/source/_build
|
||||||
tools/*.txt
|
tools/*.txt
|
||||||
|
playground/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -5,7 +5,22 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [9.3.0] - Unreleased
|
## [9.4.0] - Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added rich.live https://github.com/willmcgugan/rich/pull/382
|
||||||
|
- Added algin parameter to Rule and Console.rule
|
||||||
|
- Added rich.Status class and Console.status
|
||||||
|
- Added getitem to Text
|
||||||
|
- Added style parameter to Console.log
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Table.add_row style argument now applies to entire line and not just cells
|
||||||
|
- Added end_section parameter to Table.add_row to force a line underneath row
|
||||||
|
|
||||||
|
## [9.3.0] - 2020-12-1
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
@ -13,6 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Added get_time parameter to Console
|
- Added get_time parameter to Console
|
||||||
- Added rich.abc.RichRenderable
|
- Added rich.abc.RichRenderable
|
||||||
- Added expand_all to rich.pretty.install()
|
- 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
|
### Changed
|
||||||
|
|
||||||
|
@ -23,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Fixed redirecting of stderr in Progress
|
- Fixed redirecting of stderr in Progress
|
||||||
- Fixed broken expanded tuple of one https://github.com/willmcgugan/rich/issues/445
|
- Fixed broken expanded tuple of one https://github.com/willmcgugan/rich/issues/445
|
||||||
- Fixed traceback message with `from` exceptions
|
- Fixed traceback message with `from` exceptions
|
||||||
|
- Fixed justify argument not working in console.log https://github.com/willmcgugan/rich/issues/460
|
||||||
|
|
||||||
## [9.2.0] - 2020-11-08
|
## [9.2.0] - 2020-11-08
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,24 @@
|
||||||
# Contributing to Rich
|
# 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
|
## 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
|
poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -29,7 +40,8 @@ New code should ideally have tests and not break existing tests.
|
||||||
|
|
||||||
### Type Checking
|
### 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
|
make typecheck
|
||||||
|
|
|
@ -7,3 +7,4 @@ The following people have contributed to the development of Rich:
|
||||||
- [Oleksis Fraga](https://github.com/oleksis)
|
- [Oleksis Fraga](https://github.com/oleksis)
|
||||||
- [Hedy Li](https://github.com/hedythedev)
|
- [Hedy Li](https://github.com/hedythedev)
|
||||||
- [Will McGugan](https://github.com/willmcgugan)
|
- [Will McGugan](https://github.com/willmcgugan)
|
||||||
|
- [Nathan Page](https://github.com/nathanrpage97)
|
||||||
|
|
|
@ -205,6 +205,7 @@ Rich 可以将内容通过排列整齐的,具有相等或最佳的宽度的[
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from rich import print
|
from rich import print
|
||||||
from rich.columns import Columns
|
from rich.columns import Columns
|
||||||
|
|
|
@ -219,6 +219,7 @@ Rich puede representar contenido en [columnas](https://rich.readthedocs.io/en/la
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from rich import print
|
from rich import print
|
||||||
from rich.columns import Columns
|
from rich.columns import Columns
|
||||||
|
|
29
README.md
29
README.md
|
@ -31,6 +31,12 @@ Install with `pip` or your favorite PyPi package manager.
|
||||||
pip install rich
|
pip install rich
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run the following to test Rich output on your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m rich
|
||||||
|
```
|
||||||
|
|
||||||
## Rich print function
|
## 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:
|
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:
|
||||||
|
@ -224,12 +230,35 @@ The columns may be configured to show any details you want. Built-in columns inc
|
||||||
|
|
||||||
To try this out yourself, see [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) which can download multiple URLs simultaneously while displaying progress.
|
To try this out yourself, see [examples/downloader.py](https://github.com/willmcgugan/rich/blob/master/examples/downloader.py) which can download multiple URLs simultaneously while displaying progress.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
For situations where it is hard to calculate progress, you can use the status method which will display a 'spinner' animation with a status message that won't prevent you from writing to the terminal. Here's an example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from time import sleep
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
tasks = [f"task {n}" for n in range(1, 11)]
|
||||||
|
|
||||||
|
with console.status("[bold green]Working on tasks...") as status:
|
||||||
|
while tasks:
|
||||||
|
task = tasks.pop(0)
|
||||||
|
sleep(1)
|
||||||
|
console.log(f"{task} complete")
|
||||||
|
```
|
||||||
|
|
||||||
|
This generates the following output in the terminal.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Columns
|
## Columns
|
||||||
|
|
||||||
Rich can render content in neat [columns](https://rich.readthedocs.io/en/latest/columns.html) with equal or optimal width. Here's a very basic clone of the (MacOS / Linux) `ls` command which displays a directory listing in columns:
|
Rich can render content in neat [columns](https://rich.readthedocs.io/en/latest/columns.html) with equal or optimal width. Here's a very basic clone of the (MacOS / Linux) `ls` command which displays a directory listing in columns:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
from rich import print
|
from rich import print
|
||||||
from rich.columns import Columns
|
from rich.columns import Columns
|
||||||
|
|
|
@ -69,6 +69,38 @@ The :meth:`~rich.console.Console.log` methods offers the same capabilities as pr
|
||||||
|
|
||||||
To help with debugging, the log() method has a ``log_locals`` parameter. If you set this to ``True``, Rich will display a table of local variables where the method was called.
|
To help with debugging, the log() method has a ``log_locals`` parameter. If you set this to ``True``, Rich will display a table of local variables where the method was called.
|
||||||
|
|
||||||
|
|
||||||
|
Rules
|
||||||
|
-----
|
||||||
|
|
||||||
|
The :meth:`~rich.console.Console.rule` method will draw a horizontal line with an optional title, which is a good way of dividing your terminal output in to sections.
|
||||||
|
|
||||||
|
>>> console.rule("[bold red]Chapter 2")
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<pre style="font-family:Menlo,\'DejaVu Sans Mono\',consolas,\'Courier New\',monospace"><span style="color: #00ff00">─────────────────────────────── </span><span style="color: #800000; font-weight: bold">Chapter 2</span><span style="color: #00ff00"> ───────────────────────────────</span></pre>
|
||||||
|
|
||||||
|
The rule method also accepts a ``style`` parameter to set the style of the line, and an ``align`` parameter to align the title ("left", "center", or "right").
|
||||||
|
|
||||||
|
|
||||||
|
Status
|
||||||
|
------
|
||||||
|
|
||||||
|
Rich can display a status message with a 'spinner' animation that won't interfere with regular console output. Run the following command for a demo of this feature::
|
||||||
|
|
||||||
|
python -m rich.status
|
||||||
|
|
||||||
|
To display a status message call :meth:`~rich.console.Console.status` with the status message (which may be a string, Text, or other renderable). The result is a context manager which starts and stop the status display around a block of code. Here's an example::
|
||||||
|
|
||||||
|
with console.status("Working...")
|
||||||
|
do_work()
|
||||||
|
|
||||||
|
You can change the spinner animation via the ``spinner`` parameter. Run the following command to see the available choices::
|
||||||
|
|
||||||
|
python -m rich.spinner
|
||||||
|
|
||||||
|
|
||||||
Low level output
|
Low level output
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ Welcome to Rich's documentation!
|
||||||
panel.rst
|
panel.rst
|
||||||
group.rst
|
group.rst
|
||||||
columns.rst
|
columns.rst
|
||||||
|
live.rst
|
||||||
progress.rst
|
progress.rst
|
||||||
markdown.rst
|
markdown.rst
|
||||||
syntax.rst
|
syntax.rst
|
||||||
|
|
172
docs/source/live.rst
Normal file
172
docs/source/live.rst
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
.. _live:
|
||||||
|
|
||||||
|
Live Display
|
||||||
|
============
|
||||||
|
|
||||||
|
Rich can display continiuously updated information for any renderable.
|
||||||
|
|
||||||
|
To see some live display examples, try this from the command line::
|
||||||
|
|
||||||
|
python -m rich.live
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you see ellipsis "...", this indicates that the terminal is not tall enough to show the full table.
|
||||||
|
|
||||||
|
Basic Usage
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The basic usage can be split into two use cases.
|
||||||
|
|
||||||
|
1. Same Renderable
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When keeping the same renderable, you simply pass the :class:`~rich.console.RenderableType` you would like to see updating and provide
|
||||||
|
a ``refresh_per_second`` parameter. The Live :class:`~rich.live.Live` will automatically update the console at the provided refresh rate.
|
||||||
|
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rich.live import Live
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
table = Table()
|
||||||
|
table.add_column("Row ID")
|
||||||
|
table.add_column("Description")
|
||||||
|
table.add_column("Level")
|
||||||
|
|
||||||
|
with Live(table, refresh_per_second=4): # update 4 times a second to feel fluid
|
||||||
|
for row in range(12):
|
||||||
|
time.sleep(0.4) # arbitrary delay
|
||||||
|
# update the renderable internally
|
||||||
|
table.add_row(f"{row}", f"description {row}", "[red]ERROR")
|
||||||
|
|
||||||
|
|
||||||
|
2. New Renderable
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can also provide constant new renderable to :class:`~rich.live.Live` using the :meth:`~rich.live.Live.update` function. This allows you to
|
||||||
|
completely change what is rendered live.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rich.live import Live
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
|
||||||
|
def generate_table() -> Table:
|
||||||
|
|
||||||
|
table = Table()
|
||||||
|
table.add_column("ID")
|
||||||
|
table.add_column("Value")
|
||||||
|
table.add_column("Status")
|
||||||
|
|
||||||
|
for row in range(random.randint(2, 6)):
|
||||||
|
value = random.random() * 100
|
||||||
|
table.add_row(
|
||||||
|
f"{row}", f"{value:3.2f}", "[red]ERROR" if value < 50 else "[green]SUCCESS"
|
||||||
|
)
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
with Live(refresh_per_second=4) as live:
|
||||||
|
for _ in range(40):
|
||||||
|
time.sleep(0.4)
|
||||||
|
live.update(generate_table())
|
||||||
|
|
||||||
|
Advanced Usage
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Transient Display
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Normally when you exit live context manager (or call :meth:`~rich.live.Live.stop`) the last refreshed item remains in the terminal with the cursor on the following line.
|
||||||
|
You can also make the live display disappear on exit by setting ``transient=True`` on the Live constructor. Here's an example::
|
||||||
|
|
||||||
|
with Live(transient=True) as live:
|
||||||
|
...
|
||||||
|
|
||||||
|
Auto refresh
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, the live display will refresh 4 times a second. You can set the refresh rate with the ``refresh_per_second`` argument on the :class:`~rich.live.Live` constructor.
|
||||||
|
You should set this to something lower than 4 if you know your updates will not be that frequent or higher for a smoother feeling.
|
||||||
|
|
||||||
|
You might want to disable auto-refresh entirely if your updates are not very frequent, which you can do by setting ``auto_refresh=False`` on the constructor.
|
||||||
|
If you disable auto-refresh you will need to call :meth:`~rich.live.Live.refresh` manually or :meth:`~rich.live.Live.update` with ``refresh=True``.
|
||||||
|
|
||||||
|
Vertical Overflow
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
By default, the live display will display ellipsis if the renderable is too large for the terminal. You can adjust this by setting the
|
||||||
|
``vertical_overflow`` argument on the :class:`~rich.live.Live` constructor.
|
||||||
|
|
||||||
|
- crop: Show renderable up to the terminal height. The rest is hidden.
|
||||||
|
- ellipsis: Similar to crop except last line of the terminal is replaced with "...". This is the default behavior.
|
||||||
|
- visible: Will allow the whole renderable to be shown. Note that the display cannot be properly cleared in this mode.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Once the live display stops on a non-transient renderable, the last frame will render as **visible** since it doesn't have to be cleared.
|
||||||
|
|
||||||
|
Complex Renders
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Refer to the :ref:`Render Groups` about combining multiple :class:`RenderableType` together so that it may be passed into the :class:`~rich.live.Live` constructor
|
||||||
|
or :meth:`~rich.live.Live.update` method.
|
||||||
|
|
||||||
|
For more powerful structuring it is also possible to use nested tables.
|
||||||
|
|
||||||
|
|
||||||
|
Print / log
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
The Live class will create an internal Console object which you can access via ``live.console``. If you print or log to this console, the output will be displayed *above* the live display. Here's an example::
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rich.live import Live
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
table = Table()
|
||||||
|
table.add_column("Row ID")
|
||||||
|
table.add_column("Description")
|
||||||
|
table.add_column("Level")
|
||||||
|
|
||||||
|
with Live(table, refresh_per_second=4): # update 4 times a second to feel fluid
|
||||||
|
for row in range(12):
|
||||||
|
live.console.print("Working on row #{row}")
|
||||||
|
time.sleep(0.4)
|
||||||
|
table.add_row(f"{row}", f"description {row}", "[red]ERROR")
|
||||||
|
|
||||||
|
|
||||||
|
If you have another Console object you want to use, pass it in to the :class:`~rich.live.Live` constructor. Here's an example::
|
||||||
|
|
||||||
|
from my_project import my_console
|
||||||
|
|
||||||
|
with Live(console=my_console) as live:
|
||||||
|
my_console.print("[bold blue]Starting work!")
|
||||||
|
...
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you are passing in a file console, the live display only show the last item once the live context is left.
|
||||||
|
|
||||||
|
Redirecting stdout / stderr
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To avoid breaking the live display visuals, Rich will redirect ``stdout`` and ``stderr`` so that you can use the builtin ``print`` statement.
|
||||||
|
This feature is enabled by default, but you can disable by setting ``redirect_stdout`` or ``redirect_stderr`` to ``False``.
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
See `table_movie.py <https://github.com/willmcgugan/rich/blob/master/examples/table_movie.py>`_ and
|
||||||
|
`top_lite_simulator.py <https://github.com/willmcgugan/rich/blob/master/examples/top_lite_simulator.py>`_
|
||||||
|
for deeper examples of live displaying.
|
|
@ -114,6 +114,8 @@ The following column objects are available:
|
||||||
- :class:`~rich.progress.TotalFileSizeColumn` Displays total file size (assumes the steps are bytes).
|
- :class:`~rich.progress.TotalFileSizeColumn` Displays total file size (assumes the steps are bytes).
|
||||||
- :class:`~rich.progress.DownloadColumn` Displays download progress (assumes the steps are bytes).
|
- :class:`~rich.progress.DownloadColumn` Displays download progress (assumes the steps are bytes).
|
||||||
- :class:`~rich.progress.TransferSpeedColumn` Displays transfer speed (assumes the steps are bytes.
|
- :class:`~rich.progress.TransferSpeedColumn` Displays transfer speed (assumes the steps are bytes.
|
||||||
|
- :class:`~rich.progress.SpinnerColumn` Displays a "spinner" animation.
|
||||||
|
- :class:`~rich.progress.RenderableColumn` Displays an arbitrary Rich renderable in the column.
|
||||||
|
|
||||||
To implement your own columns, extend the :class:`~rich.progress.Progress` and use it as you would the other columns.
|
To implement your own columns, extend the :class:`~rich.progress.Progress` and use it as you would the other columns.
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ Reference
|
||||||
reference/emoji.rst
|
reference/emoji.rst
|
||||||
reference/highlighter.rst
|
reference/highlighter.rst
|
||||||
reference/init.rst
|
reference/init.rst
|
||||||
|
reference/live.rst
|
||||||
reference/logging.rst
|
reference/logging.rst
|
||||||
reference/markdown.rst
|
reference/markdown.rst
|
||||||
reference/markup.rst
|
reference/markup.rst
|
||||||
|
@ -25,6 +26,8 @@ Reference
|
||||||
reference/protocol.rst
|
reference/protocol.rst
|
||||||
reference/rule.rst
|
reference/rule.rst
|
||||||
reference/segment.rst
|
reference/segment.rst
|
||||||
|
reference/spinner.rst
|
||||||
|
reference/status.rst
|
||||||
reference/style.rst
|
reference/style.rst
|
||||||
reference/styled.rst
|
reference/styled.rst
|
||||||
reference/syntax.rst
|
reference/syntax.rst
|
||||||
|
|
5
docs/source/reference/live.rst
Normal file
5
docs/source/reference/live.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
rich.live
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. automodule:: rich.live
|
||||||
|
:members:
|
5
docs/source/reference/spinner.rst
Normal file
5
docs/source/reference/spinner.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
rich.spinner
|
||||||
|
============
|
||||||
|
|
||||||
|
.. automodule:: rich.spinner
|
||||||
|
:members:
|
5
docs/source/reference/status.rst
Normal file
5
docs/source/reference/status.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
rich.status
|
||||||
|
============
|
||||||
|
|
||||||
|
.. automodule:: rich.status
|
||||||
|
:members:
|
13
examples/status.py
Normal file
13
examples/status.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from time import sleep
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
console.print()
|
||||||
|
|
||||||
|
tasks = [f"task {n}" for n in range(1, 11)]
|
||||||
|
|
||||||
|
with console.status("[bold green]Working on tasks...") as status:
|
||||||
|
while tasks:
|
||||||
|
task = tasks.pop(0)
|
||||||
|
sleep(1)
|
||||||
|
console.log(f"{task} complete")
|
|
@ -1,12 +1,16 @@
|
||||||
|
"""Same as the table_movie.py but uses Live to update"""
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
from rich.columns import Columns
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.measure import Measurement
|
from rich.measure import Measurement
|
||||||
from rich import box
|
from rich import box
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
|
from rich.live import Live
|
||||||
|
|
||||||
TABLE_DATA = [
|
TABLE_DATA = [
|
||||||
[
|
[
|
||||||
"May 25, 1977",
|
"May 25, 1977",
|
||||||
|
@ -60,136 +64,110 @@ BEAT_TIME = 0.04
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def beat(length: int = 1) -> None:
|
def beat(length: int = 1) -> None:
|
||||||
with console:
|
with console:
|
||||||
console.clear()
|
|
||||||
yield
|
yield
|
||||||
time.sleep(length * BEAT_TIME)
|
time.sleep(length * BEAT_TIME)
|
||||||
|
|
||||||
|
|
||||||
table = Table(show_footer=False)
|
table = Table(show_footer=False)
|
||||||
|
table_centered = Columns((table,), align="center", expand=True)
|
||||||
|
|
||||||
|
|
||||||
console.clear()
|
console.clear()
|
||||||
console.show_cursor(False)
|
|
||||||
try:
|
|
||||||
|
|
||||||
|
with Live(
|
||||||
|
table_centered, console=console, refresh_per_second=10, vertical_overflow="ellipsis"
|
||||||
|
):
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.add_column("Release Date", no_wrap=True)
|
table.add_column("Release Date", no_wrap=True)
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.add_column("Title", Text.from_markup("[b]Total", justify="right"))
|
table.add_column("Title", Text.from_markup("[b]Total", justify="right"))
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.add_column("Budget", "[u]$412,000,000", no_wrap=True)
|
table.add_column("Budget", "[u]$412,000,000", no_wrap=True)
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.add_column("Opening Weekend", "[u]$577,703,455", no_wrap=True)
|
table.add_column("Opening Weekend", "[u]$577,703,455", no_wrap=True)
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.add_column("Box Office", "[u]$4,331,212,357", no_wrap=True)
|
table.add_column("Box Office", "[u]$4,331,212,357", no_wrap=True)
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.title = "Star Wars Box Office"
|
table.title = "Star Wars Box Office"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.title = (
|
table.title = (
|
||||||
"[not italic]:popcorn:[/] Star Wars Box Office [not italic]:popcorn:[/]"
|
"[not italic]:popcorn:[/] Star Wars Box Office [not italic]:popcorn:[/]"
|
||||||
)
|
)
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.caption = "Made with Rich"
|
table.caption = "Made with Rich"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.caption = "Made with [b]Rich[/b]"
|
table.caption = "Made with [b]Rich[/b]"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
table.caption = "Made with [b magenta not dim]Rich[/]"
|
|
||||||
with beat(10):
|
with beat(10):
|
||||||
console.print(table, justify="center")
|
table.caption = "Made with [b magenta not dim]Rich[/]"
|
||||||
|
|
||||||
for row in TABLE_DATA:
|
for row in TABLE_DATA:
|
||||||
|
with beat(10):
|
||||||
table.add_row(*row)
|
table.add_row(*row)
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
table.show_footer = True
|
|
||||||
with beat(10):
|
with beat(10):
|
||||||
console.print(table, justify="center")
|
table.show_footer = True
|
||||||
|
|
||||||
table_width = Measurement.get(console, table, console.width).maximum
|
table_width = Measurement.get(console, table, console.width).maximum
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[2].justify = "right"
|
table.columns[2].justify = "right"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[3].justify = "right"
|
table.columns[3].justify = "right"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[4].justify = "right"
|
table.columns[4].justify = "right"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[2].header_style = "bold red"
|
table.columns[2].header_style = "bold red"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[3].header_style = "bold green"
|
table.columns[3].header_style = "bold green"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[4].header_style = "bold blue"
|
table.columns[4].header_style = "bold blue"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[2].style = "red"
|
table.columns[2].style = "red"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[3].style = "green"
|
table.columns[3].style = "green"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[4].style = "blue"
|
table.columns[4].style = "blue"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[0].style = "cyan"
|
table.columns[0].style = "cyan"
|
||||||
table.columns[0].header_style = "bold cyan"
|
table.columns[0].header_style = "bold cyan"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[1].style = "magenta"
|
table.columns[1].style = "magenta"
|
||||||
table.columns[1].header_style = "bold magenta"
|
table.columns[1].header_style = "bold magenta"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[2].footer_style = "bright_red"
|
table.columns[2].footer_style = "bright_red"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[3].footer_style = "bright_green"
|
table.columns[3].footer_style = "bright_green"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.columns[4].footer_style = "bright_blue"
|
table.columns[4].footer_style = "bright_blue"
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(10):
|
||||||
table.row_styles = ["none", "dim"]
|
table.row_styles = ["none", "dim"]
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
table.border_style = "bright_yellow"
|
|
||||||
with beat(10):
|
with beat(10):
|
||||||
console.print(table, justify="center")
|
table.border_style = "bright_yellow"
|
||||||
|
|
||||||
for box in [
|
for box in [
|
||||||
box.SQUARE,
|
box.SQUARE,
|
||||||
|
@ -197,39 +175,29 @@ try:
|
||||||
box.SIMPLE,
|
box.SIMPLE,
|
||||||
box.SIMPLE_HEAD,
|
box.SIMPLE_HEAD,
|
||||||
]:
|
]:
|
||||||
|
with beat(10):
|
||||||
table.box = box
|
table.box = box
|
||||||
with beat(10):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
table.pad_edge = False
|
|
||||||
with beat(10):
|
with beat(10):
|
||||||
console.print(table, justify="center")
|
table.pad_edge = False
|
||||||
|
|
||||||
original_width = Measurement.get(console, table).maximum
|
original_width = Measurement.get(console, table).maximum
|
||||||
|
|
||||||
for width in range(original_width, console.width, 2):
|
for width in range(original_width, console.width, 2):
|
||||||
table.width = width
|
|
||||||
with beat(2):
|
with beat(2):
|
||||||
console.print(table, justify="center")
|
table.width = width
|
||||||
|
|
||||||
for width in range(console.width, original_width, -2):
|
for width in range(console.width, original_width, -2):
|
||||||
table.width = width
|
|
||||||
with beat(2):
|
with beat(2):
|
||||||
console.print(table, justify="center")
|
table.width = width
|
||||||
|
|
||||||
for width in range(original_width, 90, -2):
|
for width in range(original_width, 90, -2):
|
||||||
table.width = width
|
|
||||||
with beat(2):
|
with beat(2):
|
||||||
console.print(table, justify="center")
|
table.width = width
|
||||||
|
|
||||||
for width in range(90, original_width + 1, 2):
|
for width in range(90, original_width + 1, 2):
|
||||||
|
with beat(2):
|
||||||
table.width = width
|
table.width = width
|
||||||
with beat(2):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
|
with beat(2):
|
||||||
table.width = None
|
table.width = None
|
||||||
with beat(2):
|
|
||||||
console.print(table, justify="center")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
console.show_cursor(True)
|
|
||||||
|
|
81
examples/top_lite_simulator.py
Normal file
81
examples/top_lite_simulator.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
"""Lite simulation of the top linux command."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from rich import box
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.live import Live
|
||||||
|
from rich.table import Table
|
||||||
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Process:
|
||||||
|
pid: int
|
||||||
|
command: str
|
||||||
|
cpu_percent: float
|
||||||
|
memory: int
|
||||||
|
start_time: datetime.datetime
|
||||||
|
thread_count: int
|
||||||
|
state: Literal["running", "sleeping"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def memory_str(self) -> str:
|
||||||
|
if self.memory > 1e6:
|
||||||
|
return f"{int(self.memory/1e6)}M"
|
||||||
|
if self.memory > 1e3:
|
||||||
|
return f"{int(self.memory/1e3)}K"
|
||||||
|
return str(self.memory)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_str(self) -> str:
|
||||||
|
return str(datetime.datetime.now() - self.start_time)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_process(pid: int) -> Process:
|
||||||
|
return Process(
|
||||||
|
pid=pid,
|
||||||
|
command=f"Process {pid}",
|
||||||
|
cpu_percent=random.random() * 20,
|
||||||
|
memory=random.randint(10, 200) ** 3,
|
||||||
|
start_time=datetime.datetime.now()
|
||||||
|
- datetime.timedelta(seconds=random.randint(0, 500) ** 2),
|
||||||
|
thread_count=random.randint(1, 32),
|
||||||
|
state="running" if random.randint(0, 10) < 8 else "sleeping",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_process_table(height: int) -> Table:
|
||||||
|
|
||||||
|
processes = sorted(
|
||||||
|
[generate_process(pid) for pid in range(height)],
|
||||||
|
key=lambda p: p.cpu_percent,
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
table = Table(
|
||||||
|
"PID", "Command", "CPU %", "Memory", "Time", "Thread #", "State", box=box.SIMPLE
|
||||||
|
)
|
||||||
|
|
||||||
|
for process in processes:
|
||||||
|
table.add_row(
|
||||||
|
str(process.pid),
|
||||||
|
process.command,
|
||||||
|
f"{process.cpu_percent:.1f}",
|
||||||
|
process.memory_str,
|
||||||
|
process.time_str,
|
||||||
|
str(process.thread_count),
|
||||||
|
process.state,
|
||||||
|
)
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
with Live(console=console, transient=True, auto_refresh=False) as live:
|
||||||
|
while True:
|
||||||
|
live.update(create_process_table(console.size.height - 4), refresh=True)
|
||||||
|
time.sleep(1)
|
Binary file not shown.
Before Width: | Height: | Size: 729 KiB After Width: | Height: | Size: 718 KiB |
BIN
imgs/status.gif
Normal file
BIN
imgs/status.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 588 KiB |
376
poetry.lock
generated
376
poetry.lock
generated
|
@ -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]]
|
[[package]]
|
||||||
name = "appnope"
|
name = "appnope"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -6,12 +14,20 @@ category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = "*"
|
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]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "19.3.0"
|
version = "19.3.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
@ -28,6 +44,29 @@ category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = "*"
|
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]]
|
[[package]]
|
||||||
name = "bleach"
|
name = "bleach"
|
||||||
version = "3.1.5"
|
version = "3.1.5"
|
||||||
|
@ -41,6 +80,14 @@ packaging = "*"
|
||||||
six = ">=1.9.0"
|
six = ">=1.9.0"
|
||||||
webencodings = "*"
|
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]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -60,6 +107,17 @@ python-versions = "*"
|
||||||
[package.extras]
|
[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]]
|
[[package]]
|
||||||
name = "dataclasses"
|
name = "dataclasses"
|
||||||
version = "0.8"
|
version = "0.8"
|
||||||
|
@ -97,7 +155,7 @@ name = "importlib-metadata"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -107,6 +165,14 @@ zipp = ">=0.5"
|
||||||
docs = ["sphinx", "rst.linker"]
|
docs = ["sphinx", "rst.linker"]
|
||||||
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
|
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]]
|
[[package]]
|
||||||
name = "ipykernel"
|
name = "ipykernel"
|
||||||
version = "5.3.2"
|
version = "5.3.2"
|
||||||
|
@ -275,6 +341,30 @@ category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = "*"
|
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]]
|
[[package]]
|
||||||
name = "nbconvert"
|
name = "nbconvert"
|
||||||
version = "5.6.1"
|
version = "5.6.1"
|
||||||
|
@ -351,7 +441,7 @@ name = "packaging"
|
||||||
version = "20.4"
|
version = "20.4"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -377,6 +467,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["docopt", "pytest (>=3.0.7)"]
|
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]]
|
[[package]]
|
||||||
name = "pexpect"
|
name = "pexpect"
|
||||||
version = "4.8.0"
|
version = "4.8.0"
|
||||||
|
@ -396,6 +494,20 @@ category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = "*"
|
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]]
|
[[package]]
|
||||||
name = "prometheus-client"
|
name = "prometheus-client"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -426,9 +538,17 @@ category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = "*"
|
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]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.7.2"
|
version = "2.7.3"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -439,7 +559,7 @@ name = "pyparsing"
|
||||||
version = "2.4.7"
|
version = "2.4.7"
|
||||||
description = "Python parsing module"
|
description = "Python parsing module"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -453,6 +573,44 @@ python-versions = "*"
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
six = "*"
|
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]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.1"
|
version = "2.8.1"
|
||||||
|
@ -488,6 +646,14 @@ category = "main"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
|
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]]
|
[[package]]
|
||||||
name = "send2trash"
|
name = "send2trash"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -501,7 +667,7 @@ name = "six"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
description = "Python 2 and 3 compatibility utilities"
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -528,6 +694,14 @@ python-versions = "*"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["pathlib2"]
|
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]]
|
[[package]]
|
||||||
name = "tornado"
|
name = "tornado"
|
||||||
version = "6.0.4"
|
version = "6.0.4"
|
||||||
|
@ -552,6 +726,14 @@ six = "*"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["pytest", "mock"]
|
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]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "3.7.4.3"
|
version = "3.7.4.3"
|
||||||
|
@ -592,7 +774,7 @@ name = "zipp"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = true
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
@ -605,13 +787,21 @@ jupyter = ["ipywidgets"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.6"
|
python-versions = "^3.6"
|
||||||
content-hash = "2d2f8de1abd0be719c813e5c47d7b48620c01de464894ab995485e85fb804454"
|
content-hash = "2aac9442e8c6265a9815532d55ada0835532f15a507a6c96445c5f8937f7d3f7"
|
||||||
|
|
||||||
[metadata.files]
|
[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 = [
|
appnope = [
|
||||||
{file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
|
{file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
|
||||||
{file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
|
{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 = [
|
attrs = [
|
||||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
{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-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
|
||||||
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
|
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
|
||||||
]
|
]
|
||||||
|
black = [
|
||||||
|
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
||||||
|
]
|
||||||
bleach = [
|
bleach = [
|
||||||
{file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"},
|
{file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"},
|
||||||
{file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"},
|
{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 = [
|
colorama = [
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
||||||
|
@ -632,6 +829,42 @@ commonmark = [
|
||||||
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
|
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
|
||||||
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
|
{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 = [
|
dataclasses = [
|
||||||
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
|
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
|
||||||
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
|
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
|
||||||
|
@ -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-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
|
||||||
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
|
{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 = [
|
ipykernel = [
|
||||||
{file = "ipykernel-5.3.2-py3-none-any.whl", hash = "sha256:0a5f1fc6f63241b9710b5960d314ffe44d8a18bf6674e3f28d2542b192fa318c"},
|
{file = "ipykernel-5.3.2-py3-none-any.whl", hash = "sha256:0a5f1fc6f63241b9710b5960d314ffe44d8a18bf6674e3f28d2542b192fa318c"},
|
||||||
{file = "ipykernel-5.3.2.tar.gz", hash = "sha256:89dc4bd19c7781f6d7eef0e666c59ce57beac56bb39b511544a71397b7b31cbb"},
|
{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-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
|
||||||
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
|
{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 = [
|
nbconvert = [
|
||||||
{file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"},
|
{file = "nbconvert-5.6.1-py2.py3-none-any.whl", hash = "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee"},
|
||||||
{file = "nbconvert-5.6.1.tar.gz", hash = "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523"},
|
{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-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"},
|
||||||
{file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"},
|
{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 = [
|
pexpect = [
|
||||||
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
|
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
|
||||||
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
|
{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-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
|
||||||
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
|
{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 = [
|
prometheus-client = [
|
||||||
{file = "prometheus_client-0.8.0-py2.py3-none-any.whl", hash = "sha256:983c7ac4b47478720db338f1491ef67a100b474e3bc7dafcbaefb7d0b8f9b01c"},
|
{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"},
|
{file = "prometheus_client-0.8.0.tar.gz", hash = "sha256:c6e6b706833a6bd1fd51711299edee907857be10ece535126a158f911ee80915"},
|
||||||
|
@ -770,9 +1035,13 @@ ptyprocess = [
|
||||||
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
|
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
|
||||||
{file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
|
{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 = [
|
pygments = [
|
||||||
{file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"},
|
{file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"},
|
||||||
{file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"},
|
{file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"},
|
||||||
]
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||||
|
@ -781,6 +1050,14 @@ pyparsing = [
|
||||||
pyrsistent = [
|
pyrsistent = [
|
||||||
{file = "pyrsistent-0.16.0.tar.gz", hash = "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"},
|
{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 = [
|
python-dateutil = [
|
||||||
{file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
|
{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"},
|
{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-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"},
|
||||||
{file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"},
|
{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 = [
|
send2trash = [
|
||||||
{file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
|
{file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
|
||||||
{file = "Send2Trash-1.5.0.tar.gz", hash = "sha256:60001cc07d707fe247c94f74ca6ac0d3255aabcb930529690897ca2a39db28b2"},
|
{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-py2.py3-none-any.whl", hash = "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4"},
|
||||||
{file = "testpath-0.4.4.tar.gz", hash = "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e"},
|
{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 = [
|
tornado = [
|
||||||
{file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"},
|
{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"},
|
{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-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
|
||||||
{file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
|
{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 = [
|
typing-extensions = [
|
||||||
{file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
|
{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"},
|
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
|
||||||
|
|
|
@ -36,6 +36,10 @@ ipywidgets = {version = "^7.5.1", optional = true}
|
||||||
jupyter = ["ipywidgets"]
|
jupyter = ["ipywidgets"]
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
|
pytest = "^6.1.2"
|
||||||
|
black = "^20.8b1"
|
||||||
|
mypy = "^0.790"
|
||||||
|
pytest-cov = "^2.10.1"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
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
|
|
|
@ -26,10 +26,11 @@ class ColorBox:
|
||||||
for x in range(options.max_width):
|
for x in range(options.max_width):
|
||||||
h = x / options.max_width
|
h = x / options.max_width
|
||||||
l = 0.1 + ((y / 5) * 0.7)
|
l = 0.1 + ((y / 5) * 0.7)
|
||||||
r, g, b = colorsys.hls_to_rgb(h, l, 1.0)
|
r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0)
|
||||||
yield Segment(
|
r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0)
|
||||||
"█", Style(color=Color.from_rgb(r * 255, g * 255, b * 255))
|
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()
|
yield Segment.line()
|
||||||
|
|
||||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||||
|
@ -96,7 +97,7 @@ def make_test_card() -> Table:
|
||||||
return table
|
return table
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
"Asian languages",
|
"Asian\nlanguage\nsupport",
|
||||||
":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다",
|
":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ def make_test_card() -> Table:
|
||||||
"[bold magenta]Rich[/] supports a simple [i]bbcode[/i] like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! "
|
"[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: "
|
":+1: :apple: :ant: :bear: :baguette_bread: :bus: "
|
||||||
)
|
)
|
||||||
table.add_row("Console markup", markup_example)
|
table.add_row("Markup", markup_example)
|
||||||
|
|
||||||
example_table = Table(
|
example_table = Table(
|
||||||
show_edge=False,
|
show_edge=False,
|
||||||
|
@ -201,7 +202,7 @@ Supports much of the *markdown*, __syntax__!
|
||||||
)
|
)
|
||||||
|
|
||||||
table.add_row(
|
table.add_row(
|
||||||
"And more",
|
"+more!",
|
||||||
"""Progress bars, columns, styled logging handler, tracebacks, etc...""",
|
"""Progress bars, columns, styled logging handler, tracebacks, etc...""",
|
||||||
)
|
)
|
||||||
return table
|
return table
|
||||||
|
|
|
@ -72,3 +72,11 @@ class LogRender:
|
||||||
|
|
||||||
output.add_row(*row)
|
output.add_row(*row)
|
||||||
return output
|
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")
|
||||||
|
|
848
rich/_spinners.py
Normal file
848
rich/_spinners.py
Normal file
|
@ -0,0 +1,848 @@
|
||||||
|
"""
|
||||||
|
Spinners are from:
|
||||||
|
* cli-spinners:
|
||||||
|
MIT License
|
||||||
|
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SPINNERS = {
|
||||||
|
"dots": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
||||||
|
},
|
||||||
|
"dots2": {"interval": 80, "frames": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]},
|
||||||
|
"dots3": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓"],
|
||||||
|
},
|
||||||
|
"dots4": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⠄",
|
||||||
|
"⠆",
|
||||||
|
"⠇",
|
||||||
|
"⠋",
|
||||||
|
"⠙",
|
||||||
|
"⠸",
|
||||||
|
"⠰",
|
||||||
|
"⠠",
|
||||||
|
"⠰",
|
||||||
|
"⠸",
|
||||||
|
"⠙",
|
||||||
|
"⠋",
|
||||||
|
"⠇",
|
||||||
|
"⠆",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dots5": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⠋",
|
||||||
|
"⠙",
|
||||||
|
"⠚",
|
||||||
|
"⠒",
|
||||||
|
"⠂",
|
||||||
|
"⠂",
|
||||||
|
"⠒",
|
||||||
|
"⠲",
|
||||||
|
"⠴",
|
||||||
|
"⠦",
|
||||||
|
"⠖",
|
||||||
|
"⠒",
|
||||||
|
"⠐",
|
||||||
|
"⠐",
|
||||||
|
"⠒",
|
||||||
|
"⠓",
|
||||||
|
"⠋",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dots6": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⠁",
|
||||||
|
"⠉",
|
||||||
|
"⠙",
|
||||||
|
"⠚",
|
||||||
|
"⠒",
|
||||||
|
"⠂",
|
||||||
|
"⠂",
|
||||||
|
"⠒",
|
||||||
|
"⠲",
|
||||||
|
"⠴",
|
||||||
|
"⠤",
|
||||||
|
"⠄",
|
||||||
|
"⠄",
|
||||||
|
"⠤",
|
||||||
|
"⠴",
|
||||||
|
"⠲",
|
||||||
|
"⠒",
|
||||||
|
"⠂",
|
||||||
|
"⠂",
|
||||||
|
"⠒",
|
||||||
|
"⠚",
|
||||||
|
"⠙",
|
||||||
|
"⠉",
|
||||||
|
"⠁",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dots7": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⠈",
|
||||||
|
"⠉",
|
||||||
|
"⠋",
|
||||||
|
"⠓",
|
||||||
|
"⠒",
|
||||||
|
"⠐",
|
||||||
|
"⠐",
|
||||||
|
"⠒",
|
||||||
|
"⠖",
|
||||||
|
"⠦",
|
||||||
|
"⠤",
|
||||||
|
"⠠",
|
||||||
|
"⠠",
|
||||||
|
"⠤",
|
||||||
|
"⠦",
|
||||||
|
"⠖",
|
||||||
|
"⠒",
|
||||||
|
"⠐",
|
||||||
|
"⠐",
|
||||||
|
"⠒",
|
||||||
|
"⠓",
|
||||||
|
"⠋",
|
||||||
|
"⠉",
|
||||||
|
"⠈",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dots8": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⠁",
|
||||||
|
"⠁",
|
||||||
|
"⠉",
|
||||||
|
"⠙",
|
||||||
|
"⠚",
|
||||||
|
"⠒",
|
||||||
|
"⠂",
|
||||||
|
"⠂",
|
||||||
|
"⠒",
|
||||||
|
"⠲",
|
||||||
|
"⠴",
|
||||||
|
"⠤",
|
||||||
|
"⠄",
|
||||||
|
"⠄",
|
||||||
|
"⠤",
|
||||||
|
"⠠",
|
||||||
|
"⠠",
|
||||||
|
"⠤",
|
||||||
|
"⠦",
|
||||||
|
"⠖",
|
||||||
|
"⠒",
|
||||||
|
"⠐",
|
||||||
|
"⠐",
|
||||||
|
"⠒",
|
||||||
|
"⠓",
|
||||||
|
"⠋",
|
||||||
|
"⠉",
|
||||||
|
"⠈",
|
||||||
|
"⠈",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dots9": {"interval": 80, "frames": ["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"]},
|
||||||
|
"dots10": {"interval": 80, "frames": ["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"]},
|
||||||
|
"dots11": {"interval": 100, "frames": ["⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"]},
|
||||||
|
"dots12": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⢀⠀",
|
||||||
|
"⡀⠀",
|
||||||
|
"⠄⠀",
|
||||||
|
"⢂⠀",
|
||||||
|
"⡂⠀",
|
||||||
|
"⠅⠀",
|
||||||
|
"⢃⠀",
|
||||||
|
"⡃⠀",
|
||||||
|
"⠍⠀",
|
||||||
|
"⢋⠀",
|
||||||
|
"⡋⠀",
|
||||||
|
"⠍⠁",
|
||||||
|
"⢋⠁",
|
||||||
|
"⡋⠁",
|
||||||
|
"⠍⠉",
|
||||||
|
"⠋⠉",
|
||||||
|
"⠋⠉",
|
||||||
|
"⠉⠙",
|
||||||
|
"⠉⠙",
|
||||||
|
"⠉⠩",
|
||||||
|
"⠈⢙",
|
||||||
|
"⠈⡙",
|
||||||
|
"⢈⠩",
|
||||||
|
"⡀⢙",
|
||||||
|
"⠄⡙",
|
||||||
|
"⢂⠩",
|
||||||
|
"⡂⢘",
|
||||||
|
"⠅⡘",
|
||||||
|
"⢃⠨",
|
||||||
|
"⡃⢐",
|
||||||
|
"⠍⡐",
|
||||||
|
"⢋⠠",
|
||||||
|
"⡋⢀",
|
||||||
|
"⠍⡁",
|
||||||
|
"⢋⠁",
|
||||||
|
"⡋⠁",
|
||||||
|
"⠍⠉",
|
||||||
|
"⠋⠉",
|
||||||
|
"⠋⠉",
|
||||||
|
"⠉⠙",
|
||||||
|
"⠉⠙",
|
||||||
|
"⠉⠩",
|
||||||
|
"⠈⢙",
|
||||||
|
"⠈⡙",
|
||||||
|
"⠈⠩",
|
||||||
|
"⠀⢙",
|
||||||
|
"⠀⡙",
|
||||||
|
"⠀⠩",
|
||||||
|
"⠀⢘",
|
||||||
|
"⠀⡘",
|
||||||
|
"⠀⠨",
|
||||||
|
"⠀⢐",
|
||||||
|
"⠀⡐",
|
||||||
|
"⠀⠠",
|
||||||
|
"⠀⢀",
|
||||||
|
"⠀⡀",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dots8Bit": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"⠀",
|
||||||
|
"⠁",
|
||||||
|
"⠂",
|
||||||
|
"⠃",
|
||||||
|
"⠄",
|
||||||
|
"⠅",
|
||||||
|
"⠆",
|
||||||
|
"⠇",
|
||||||
|
"⡀",
|
||||||
|
"⡁",
|
||||||
|
"⡂",
|
||||||
|
"⡃",
|
||||||
|
"⡄",
|
||||||
|
"⡅",
|
||||||
|
"⡆",
|
||||||
|
"⡇",
|
||||||
|
"⠈",
|
||||||
|
"⠉",
|
||||||
|
"⠊",
|
||||||
|
"⠋",
|
||||||
|
"⠌",
|
||||||
|
"⠍",
|
||||||
|
"⠎",
|
||||||
|
"⠏",
|
||||||
|
"⡈",
|
||||||
|
"⡉",
|
||||||
|
"⡊",
|
||||||
|
"⡋",
|
||||||
|
"⡌",
|
||||||
|
"⡍",
|
||||||
|
"⡎",
|
||||||
|
"⡏",
|
||||||
|
"⠐",
|
||||||
|
"⠑",
|
||||||
|
"⠒",
|
||||||
|
"⠓",
|
||||||
|
"⠔",
|
||||||
|
"⠕",
|
||||||
|
"⠖",
|
||||||
|
"⠗",
|
||||||
|
"⡐",
|
||||||
|
"⡑",
|
||||||
|
"⡒",
|
||||||
|
"⡓",
|
||||||
|
"⡔",
|
||||||
|
"⡕",
|
||||||
|
"⡖",
|
||||||
|
"⡗",
|
||||||
|
"⠘",
|
||||||
|
"⠙",
|
||||||
|
"⠚",
|
||||||
|
"⠛",
|
||||||
|
"⠜",
|
||||||
|
"⠝",
|
||||||
|
"⠞",
|
||||||
|
"⠟",
|
||||||
|
"⡘",
|
||||||
|
"⡙",
|
||||||
|
"⡚",
|
||||||
|
"⡛",
|
||||||
|
"⡜",
|
||||||
|
"⡝",
|
||||||
|
"⡞",
|
||||||
|
"⡟",
|
||||||
|
"⠠",
|
||||||
|
"⠡",
|
||||||
|
"⠢",
|
||||||
|
"⠣",
|
||||||
|
"⠤",
|
||||||
|
"⠥",
|
||||||
|
"⠦",
|
||||||
|
"⠧",
|
||||||
|
"⡠",
|
||||||
|
"⡡",
|
||||||
|
"⡢",
|
||||||
|
"⡣",
|
||||||
|
"⡤",
|
||||||
|
"⡥",
|
||||||
|
"⡦",
|
||||||
|
"⡧",
|
||||||
|
"⠨",
|
||||||
|
"⠩",
|
||||||
|
"⠪",
|
||||||
|
"⠫",
|
||||||
|
"⠬",
|
||||||
|
"⠭",
|
||||||
|
"⠮",
|
||||||
|
"⠯",
|
||||||
|
"⡨",
|
||||||
|
"⡩",
|
||||||
|
"⡪",
|
||||||
|
"⡫",
|
||||||
|
"⡬",
|
||||||
|
"⡭",
|
||||||
|
"⡮",
|
||||||
|
"⡯",
|
||||||
|
"⠰",
|
||||||
|
"⠱",
|
||||||
|
"⠲",
|
||||||
|
"⠳",
|
||||||
|
"⠴",
|
||||||
|
"⠵",
|
||||||
|
"⠶",
|
||||||
|
"⠷",
|
||||||
|
"⡰",
|
||||||
|
"⡱",
|
||||||
|
"⡲",
|
||||||
|
"⡳",
|
||||||
|
"⡴",
|
||||||
|
"⡵",
|
||||||
|
"⡶",
|
||||||
|
"⡷",
|
||||||
|
"⠸",
|
||||||
|
"⠹",
|
||||||
|
"⠺",
|
||||||
|
"⠻",
|
||||||
|
"⠼",
|
||||||
|
"⠽",
|
||||||
|
"⠾",
|
||||||
|
"⠿",
|
||||||
|
"⡸",
|
||||||
|
"⡹",
|
||||||
|
"⡺",
|
||||||
|
"⡻",
|
||||||
|
"⡼",
|
||||||
|
"⡽",
|
||||||
|
"⡾",
|
||||||
|
"⡿",
|
||||||
|
"⢀",
|
||||||
|
"⢁",
|
||||||
|
"⢂",
|
||||||
|
"⢃",
|
||||||
|
"⢄",
|
||||||
|
"⢅",
|
||||||
|
"⢆",
|
||||||
|
"⢇",
|
||||||
|
"⣀",
|
||||||
|
"⣁",
|
||||||
|
"⣂",
|
||||||
|
"⣃",
|
||||||
|
"⣄",
|
||||||
|
"⣅",
|
||||||
|
"⣆",
|
||||||
|
"⣇",
|
||||||
|
"⢈",
|
||||||
|
"⢉",
|
||||||
|
"⢊",
|
||||||
|
"⢋",
|
||||||
|
"⢌",
|
||||||
|
"⢍",
|
||||||
|
"⢎",
|
||||||
|
"⢏",
|
||||||
|
"⣈",
|
||||||
|
"⣉",
|
||||||
|
"⣊",
|
||||||
|
"⣋",
|
||||||
|
"⣌",
|
||||||
|
"⣍",
|
||||||
|
"⣎",
|
||||||
|
"⣏",
|
||||||
|
"⢐",
|
||||||
|
"⢑",
|
||||||
|
"⢒",
|
||||||
|
"⢓",
|
||||||
|
"⢔",
|
||||||
|
"⢕",
|
||||||
|
"⢖",
|
||||||
|
"⢗",
|
||||||
|
"⣐",
|
||||||
|
"⣑",
|
||||||
|
"⣒",
|
||||||
|
"⣓",
|
||||||
|
"⣔",
|
||||||
|
"⣕",
|
||||||
|
"⣖",
|
||||||
|
"⣗",
|
||||||
|
"⢘",
|
||||||
|
"⢙",
|
||||||
|
"⢚",
|
||||||
|
"⢛",
|
||||||
|
"⢜",
|
||||||
|
"⢝",
|
||||||
|
"⢞",
|
||||||
|
"⢟",
|
||||||
|
"⣘",
|
||||||
|
"⣙",
|
||||||
|
"⣚",
|
||||||
|
"⣛",
|
||||||
|
"⣜",
|
||||||
|
"⣝",
|
||||||
|
"⣞",
|
||||||
|
"⣟",
|
||||||
|
"⢠",
|
||||||
|
"⢡",
|
||||||
|
"⢢",
|
||||||
|
"⢣",
|
||||||
|
"⢤",
|
||||||
|
"⢥",
|
||||||
|
"⢦",
|
||||||
|
"⢧",
|
||||||
|
"⣠",
|
||||||
|
"⣡",
|
||||||
|
"⣢",
|
||||||
|
"⣣",
|
||||||
|
"⣤",
|
||||||
|
"⣥",
|
||||||
|
"⣦",
|
||||||
|
"⣧",
|
||||||
|
"⢨",
|
||||||
|
"⢩",
|
||||||
|
"⢪",
|
||||||
|
"⢫",
|
||||||
|
"⢬",
|
||||||
|
"⢭",
|
||||||
|
"⢮",
|
||||||
|
"⢯",
|
||||||
|
"⣨",
|
||||||
|
"⣩",
|
||||||
|
"⣪",
|
||||||
|
"⣫",
|
||||||
|
"⣬",
|
||||||
|
"⣭",
|
||||||
|
"⣮",
|
||||||
|
"⣯",
|
||||||
|
"⢰",
|
||||||
|
"⢱",
|
||||||
|
"⢲",
|
||||||
|
"⢳",
|
||||||
|
"⢴",
|
||||||
|
"⢵",
|
||||||
|
"⢶",
|
||||||
|
"⢷",
|
||||||
|
"⣰",
|
||||||
|
"⣱",
|
||||||
|
"⣲",
|
||||||
|
"⣳",
|
||||||
|
"⣴",
|
||||||
|
"⣵",
|
||||||
|
"⣶",
|
||||||
|
"⣷",
|
||||||
|
"⢸",
|
||||||
|
"⢹",
|
||||||
|
"⢺",
|
||||||
|
"⢻",
|
||||||
|
"⢼",
|
||||||
|
"⢽",
|
||||||
|
"⢾",
|
||||||
|
"⢿",
|
||||||
|
"⣸",
|
||||||
|
"⣹",
|
||||||
|
"⣺",
|
||||||
|
"⣻",
|
||||||
|
"⣼",
|
||||||
|
"⣽",
|
||||||
|
"⣾",
|
||||||
|
"⣿",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"line": {"interval": 130, "frames": ["-", "\\", "|", "/"]},
|
||||||
|
"line2": {"interval": 100, "frames": ["⠂", "-", "–", "—", "–", "-"]},
|
||||||
|
"pipe": {"interval": 100, "frames": ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"]},
|
||||||
|
"simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]},
|
||||||
|
"simpleDotsScrolling": {
|
||||||
|
"interval": 200,
|
||||||
|
"frames": [". ", ".. ", "...", " ..", " .", " "],
|
||||||
|
},
|
||||||
|
"star": {"interval": 70, "frames": ["✶", "✸", "✹", "✺", "✹", "✷"]},
|
||||||
|
"star2": {"interval": 80, "frames": ["+", "x", "*"]},
|
||||||
|
"flip": {
|
||||||
|
"interval": 70,
|
||||||
|
"frames": ["_", "_", "_", "-", "`", "`", "'", "´", "-", "_", "_", "_"],
|
||||||
|
},
|
||||||
|
"hamburger": {"interval": 100, "frames": ["☱", "☲", "☴"]},
|
||||||
|
"growVertical": {
|
||||||
|
"interval": 120,
|
||||||
|
"frames": ["▁", "▃", "▄", "▅", "▆", "▇", "▆", "▅", "▄", "▃"],
|
||||||
|
},
|
||||||
|
"growHorizontal": {
|
||||||
|
"interval": 120,
|
||||||
|
"frames": ["▏", "▎", "▍", "▌", "▋", "▊", "▉", "▊", "▋", "▌", "▍", "▎"],
|
||||||
|
},
|
||||||
|
"balloon": {"interval": 140, "frames": [" ", ".", "o", "O", "@", "*", " "]},
|
||||||
|
"balloon2": {"interval": 120, "frames": [".", "o", "O", "°", "O", "o", "."]},
|
||||||
|
"noise": {"interval": 100, "frames": ["▓", "▒", "░"]},
|
||||||
|
"bounce": {"interval": 120, "frames": ["⠁", "⠂", "⠄", "⠂"]},
|
||||||
|
"boxBounce": {"interval": 120, "frames": ["▖", "▘", "▝", "▗"]},
|
||||||
|
"boxBounce2": {"interval": 100, "frames": ["▌", "▀", "▐", "▄"]},
|
||||||
|
"triangle": {"interval": 50, "frames": ["◢", "◣", "◤", "◥"]},
|
||||||
|
"arc": {"interval": 100, "frames": ["◜", "◠", "◝", "◞", "◡", "◟"]},
|
||||||
|
"circle": {"interval": 120, "frames": ["◡", "⊙", "◠"]},
|
||||||
|
"squareCorners": {"interval": 180, "frames": ["◰", "◳", "◲", "◱"]},
|
||||||
|
"circleQuarters": {"interval": 120, "frames": ["◴", "◷", "◶", "◵"]},
|
||||||
|
"circleHalves": {"interval": 50, "frames": ["◐", "◓", "◑", "◒"]},
|
||||||
|
"squish": {"interval": 100, "frames": ["╫", "╪"]},
|
||||||
|
"toggle": {"interval": 250, "frames": ["⊶", "⊷"]},
|
||||||
|
"toggle2": {"interval": 80, "frames": ["▫", "▪"]},
|
||||||
|
"toggle3": {"interval": 120, "frames": ["□", "■"]},
|
||||||
|
"toggle4": {"interval": 100, "frames": ["■", "□", "▪", "▫"]},
|
||||||
|
"toggle5": {"interval": 100, "frames": ["▮", "▯"]},
|
||||||
|
"toggle6": {"interval": 300, "frames": ["ဝ", "၀"]},
|
||||||
|
"toggle7": {"interval": 80, "frames": ["⦾", "⦿"]},
|
||||||
|
"toggle8": {"interval": 100, "frames": ["◍", "◌"]},
|
||||||
|
"toggle9": {"interval": 100, "frames": ["◉", "◎"]},
|
||||||
|
"toggle10": {"interval": 100, "frames": ["㊂", "㊀", "㊁"]},
|
||||||
|
"toggle11": {"interval": 50, "frames": ["⧇", "⧆"]},
|
||||||
|
"toggle12": {"interval": 120, "frames": ["☗", "☖"]},
|
||||||
|
"toggle13": {"interval": 80, "frames": ["=", "*", "-"]},
|
||||||
|
"arrow": {"interval": 100, "frames": ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"]},
|
||||||
|
"arrow2": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "],
|
||||||
|
},
|
||||||
|
"arrow3": {
|
||||||
|
"interval": 120,
|
||||||
|
"frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"],
|
||||||
|
},
|
||||||
|
"bouncingBar": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"[ ]",
|
||||||
|
"[= ]",
|
||||||
|
"[== ]",
|
||||||
|
"[=== ]",
|
||||||
|
"[ ===]",
|
||||||
|
"[ ==]",
|
||||||
|
"[ =]",
|
||||||
|
"[ ]",
|
||||||
|
"[ =]",
|
||||||
|
"[ ==]",
|
||||||
|
"[ ===]",
|
||||||
|
"[====]",
|
||||||
|
"[=== ]",
|
||||||
|
"[== ]",
|
||||||
|
"[= ]",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"bouncingBall": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ●)",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"( ● )",
|
||||||
|
"(● )",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"smiley": {"interval": 200, "frames": ["😄 ", "😝 "]},
|
||||||
|
"monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]},
|
||||||
|
"hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]},
|
||||||
|
"clock": {
|
||||||
|
"interval": 100,
|
||||||
|
"frames": [
|
||||||
|
"🕛 ",
|
||||||
|
"🕐 ",
|
||||||
|
"🕑 ",
|
||||||
|
"🕒 ",
|
||||||
|
"🕓 ",
|
||||||
|
"🕔 ",
|
||||||
|
"🕕 ",
|
||||||
|
"🕖 ",
|
||||||
|
"🕗 ",
|
||||||
|
"🕘 ",
|
||||||
|
"🕙 ",
|
||||||
|
"🕚 ",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]},
|
||||||
|
"material": {
|
||||||
|
"interval": 17,
|
||||||
|
"frames": [
|
||||||
|
"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"███████▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"████████▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"██████████▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"███████████▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████████▁▁▁▁▁▁▁",
|
||||||
|
"██████████████▁▁▁▁▁▁",
|
||||||
|
"██████████████▁▁▁▁▁▁",
|
||||||
|
"▁██████████████▁▁▁▁▁",
|
||||||
|
"▁██████████████▁▁▁▁▁",
|
||||||
|
"▁██████████████▁▁▁▁▁",
|
||||||
|
"▁▁██████████████▁▁▁▁",
|
||||||
|
"▁▁▁██████████████▁▁▁",
|
||||||
|
"▁▁▁▁█████████████▁▁▁",
|
||||||
|
"▁▁▁▁██████████████▁▁",
|
||||||
|
"▁▁▁▁██████████████▁▁",
|
||||||
|
"▁▁▁▁▁██████████████▁",
|
||||||
|
"▁▁▁▁▁██████████████▁",
|
||||||
|
"▁▁▁▁▁██████████████▁",
|
||||||
|
"▁▁▁▁▁▁██████████████",
|
||||||
|
"▁▁▁▁▁▁██████████████",
|
||||||
|
"▁▁▁▁▁▁▁█████████████",
|
||||||
|
"▁▁▁▁▁▁▁█████████████",
|
||||||
|
"▁▁▁▁▁▁▁▁████████████",
|
||||||
|
"▁▁▁▁▁▁▁▁████████████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁███████████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁███████████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁██████████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁██████████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁████████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
|
||||||
|
"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||||
|
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||||
|
"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||||
|
"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||||
|
"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||||
|
"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||||
|
"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||||
|
"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||||
|
"████████▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"█████████▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"███████████▁▁▁▁▁▁▁▁▁",
|
||||||
|
"████████████▁▁▁▁▁▁▁▁",
|
||||||
|
"████████████▁▁▁▁▁▁▁▁",
|
||||||
|
"██████████████▁▁▁▁▁▁",
|
||||||
|
"██████████████▁▁▁▁▁▁",
|
||||||
|
"▁██████████████▁▁▁▁▁",
|
||||||
|
"▁██████████████▁▁▁▁▁",
|
||||||
|
"▁▁▁█████████████▁▁▁▁",
|
||||||
|
"▁▁▁▁▁████████████▁▁▁",
|
||||||
|
"▁▁▁▁▁████████████▁▁▁",
|
||||||
|
"▁▁▁▁▁▁███████████▁▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁█████████▁▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁█████████▁▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁█████████▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁█████████▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁█████████▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁████████▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁████████▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁███████▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁███████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"moon": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "],
|
||||||
|
},
|
||||||
|
"runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]},
|
||||||
|
"pong": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"▐⠂ ▌",
|
||||||
|
"▐⠈ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⡀ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠈ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⡀ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠈ ▌",
|
||||||
|
"▐ ⠂▌",
|
||||||
|
"▐ ⠠▌",
|
||||||
|
"▐ ⡀▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠈ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⡀ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠈ ▌",
|
||||||
|
"▐ ⠂ ▌",
|
||||||
|
"▐ ⠠ ▌",
|
||||||
|
"▐ ⡀ ▌",
|
||||||
|
"▐⠠ ▌",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"shark": {
|
||||||
|
"interval": 120,
|
||||||
|
"frames": [
|
||||||
|
"▐|\\____________▌",
|
||||||
|
"▐_|\\___________▌",
|
||||||
|
"▐__|\\__________▌",
|
||||||
|
"▐___|\\_________▌",
|
||||||
|
"▐____|\\________▌",
|
||||||
|
"▐_____|\\_______▌",
|
||||||
|
"▐______|\\______▌",
|
||||||
|
"▐_______|\\_____▌",
|
||||||
|
"▐________|\\____▌",
|
||||||
|
"▐_________|\\___▌",
|
||||||
|
"▐__________|\\__▌",
|
||||||
|
"▐___________|\\_▌",
|
||||||
|
"▐____________|\\▌",
|
||||||
|
"▐____________/|▌",
|
||||||
|
"▐___________/|_▌",
|
||||||
|
"▐__________/|__▌",
|
||||||
|
"▐_________/|___▌",
|
||||||
|
"▐________/|____▌",
|
||||||
|
"▐_______/|_____▌",
|
||||||
|
"▐______/|______▌",
|
||||||
|
"▐_____/|_______▌",
|
||||||
|
"▐____/|________▌",
|
||||||
|
"▐___/|_________▌",
|
||||||
|
"▐__/|__________▌",
|
||||||
|
"▐_/|___________▌",
|
||||||
|
"▐/|____________▌",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"dqpb": {"interval": 100, "frames": ["d", "q", "p", "b"]},
|
||||||
|
"weather": {
|
||||||
|
"interval": 100,
|
||||||
|
"frames": [
|
||||||
|
"☀️ ",
|
||||||
|
"☀️ ",
|
||||||
|
"☀️ ",
|
||||||
|
"🌤 ",
|
||||||
|
"⛅️ ",
|
||||||
|
"🌥 ",
|
||||||
|
"☁️ ",
|
||||||
|
"🌧 ",
|
||||||
|
"🌨 ",
|
||||||
|
"🌧 ",
|
||||||
|
"🌨 ",
|
||||||
|
"🌧 ",
|
||||||
|
"🌨 ",
|
||||||
|
"⛈ ",
|
||||||
|
"🌨 ",
|
||||||
|
"🌧 ",
|
||||||
|
"🌨 ",
|
||||||
|
"☁️ ",
|
||||||
|
"🌥 ",
|
||||||
|
"⛅️ ",
|
||||||
|
"🌤 ",
|
||||||
|
"☀️ ",
|
||||||
|
"☀️ ",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"christmas": {"interval": 400, "frames": ["🌲", "🎄"]},
|
||||||
|
"grenade": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"، ",
|
||||||
|
"′ ",
|
||||||
|
" ´ ",
|
||||||
|
" ‾ ",
|
||||||
|
" ⸌",
|
||||||
|
" ⸊",
|
||||||
|
" |",
|
||||||
|
" ⁎",
|
||||||
|
" ⁕",
|
||||||
|
" ෴ ",
|
||||||
|
" ⁓",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]},
|
||||||
|
"layer": {"interval": 150, "frames": ["-", "=", "≡"]},
|
||||||
|
"betaWave": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"ρββββββ",
|
||||||
|
"βρβββββ",
|
||||||
|
"ββρββββ",
|
||||||
|
"βββρβββ",
|
||||||
|
"ββββρββ",
|
||||||
|
"βββββρβ",
|
||||||
|
"ββββββρ",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"aesthetic": {
|
||||||
|
"interval": 80,
|
||||||
|
"frames": [
|
||||||
|
"▰▱▱▱▱▱▱",
|
||||||
|
"▰▰▱▱▱▱▱",
|
||||||
|
"▰▰▰▱▱▱▱",
|
||||||
|
"▰▰▰▰▱▱▱",
|
||||||
|
"▰▰▰▰▰▱▱",
|
||||||
|
"▰▰▰▰▰▰▱",
|
||||||
|
"▰▰▰▰▰▰▰",
|
||||||
|
"▰▱▱▱▱▱▱",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
||||||
from .console import Console, ConsoleOptions, RenderResult, RenderableType
|
from .console import Console, ConsoleOptions, RenderResult, RenderableType
|
||||||
|
|
||||||
AlignValues = Literal["left", "center", "right"]
|
AlignValues = Literal["left", "center", "right"]
|
||||||
|
AlignMethod = AlignValues # TODO: deprecate AlignValues
|
||||||
|
|
||||||
|
|
||||||
class Align(JupyterMixin):
|
class Align(JupyterMixin):
|
||||||
|
@ -19,7 +20,7 @@ class Align(JupyterMixin):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
renderable (RenderableType): A console renderable.
|
renderable (RenderableType): A console renderable.
|
||||||
align (AlignValues): One of "left", "center", or "right""
|
align (AlignMethod): One of "left", "center", or "right""
|
||||||
style (StyleType, optional): An optional style to apply to the renderable.
|
style (StyleType, optional): An optional style to apply to the renderable.
|
||||||
pad (bool, optional): Pad the right with spaces. Defaults to True.
|
pad (bool, optional): Pad the right with spaces. Defaults to True.
|
||||||
width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
|
width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None.
|
||||||
|
@ -31,7 +32,7 @@ class Align(JupyterMixin):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
renderable: "RenderableType",
|
renderable: "RenderableType",
|
||||||
align: AlignValues,
|
align: AlignMethod,
|
||||||
style: StyleType = None,
|
style: StyleType = None,
|
||||||
*,
|
*,
|
||||||
pad: bool = True,
|
pad: bool = True,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Optional, Union
|
from typing import Union
|
||||||
|
|
||||||
from .color import Color
|
from .color import Color
|
||||||
from .console import Console, ConsoleOptions, RenderResult
|
from .console import Console, ConsoleOptions, RenderResult
|
||||||
|
|
|
@ -3,7 +3,7 @@ from itertools import chain
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Dict, Iterable, List, Optional, Tuple
|
from typing import Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
from .align import Align, AlignValues
|
from .align import Align, AlignMethod
|
||||||
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
from .console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||||
from .constrain import Constrain
|
from .constrain import Constrain
|
||||||
from .measure import Measurement
|
from .measure import Measurement
|
||||||
|
@ -38,7 +38,7 @@ class Columns(JupyterMixin):
|
||||||
equal: bool = False,
|
equal: bool = False,
|
||||||
column_first: bool = False,
|
column_first: bool = False,
|
||||||
right_to_left: bool = False,
|
right_to_left: bool = False,
|
||||||
align: AlignValues = None,
|
align: AlignMethod = None,
|
||||||
title: TextType = None,
|
title: TextType = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.renderables = list(renderables or [])
|
self.renderables = list(renderables or [])
|
||||||
|
|
|
@ -31,7 +31,7 @@ from typing_extensions import Literal, Protocol, runtime_checkable
|
||||||
from . import errors, themes
|
from . import errors, themes
|
||||||
from ._emoji_replace import _emoji_replace
|
from ._emoji_replace import _emoji_replace
|
||||||
from ._log_render import LogRender
|
from ._log_render import LogRender
|
||||||
from .align import Align, AlignValues
|
from .align import Align, AlignMethod
|
||||||
from .color import ColorSystem
|
from .color import ColorSystem
|
||||||
from .control import Control
|
from .control import Control
|
||||||
from .highlighter import NullHighlighter, ReprHighlighter
|
from .highlighter import NullHighlighter, ReprHighlighter
|
||||||
|
@ -42,11 +42,13 @@ from .pretty import Pretty
|
||||||
from .scope import render_scope
|
from .scope import render_scope
|
||||||
from .segment import Segment
|
from .segment import Segment
|
||||||
from .style import Style
|
from .style import Style
|
||||||
|
from .styled import Styled
|
||||||
from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
|
from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
|
||||||
from .text import Text, TextType
|
from .text import Text, TextType
|
||||||
from .theme import Theme, ThemeStack
|
from .theme import Theme, ThemeStack
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from .status import Status
|
||||||
from ._windows import WindowsConsoleFeatures
|
from ._windows import WindowsConsoleFeatures
|
||||||
|
|
||||||
WINDOWS = platform.system() == "Windows"
|
WINDOWS = platform.system() == "Windows"
|
||||||
|
@ -758,6 +760,39 @@ class Console:
|
||||||
"""
|
"""
|
||||||
self.control("\033[2J\033[H" if home else "\033[2J")
|
self.control("\033[2J\033[H" if home else "\033[2J")
|
||||||
|
|
||||||
|
def status(
|
||||||
|
self,
|
||||||
|
status: RenderableType,
|
||||||
|
spinner: str = "dots",
|
||||||
|
spinner_style: str = "status.spinner",
|
||||||
|
speed: float = 1.0,
|
||||||
|
refresh_per_second: float = 12.5,
|
||||||
|
) -> "Status":
|
||||||
|
"""Display a status and spinner.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (RenderableType): A status renderable (str or Text typically).
|
||||||
|
console (Console, optional): Console instance to use, or None for global console. Defaults to None.
|
||||||
|
spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
|
||||||
|
spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
|
||||||
|
speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
|
||||||
|
refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Status: A Status object that may be used as a context manager.
|
||||||
|
"""
|
||||||
|
from .status import Status
|
||||||
|
|
||||||
|
status_renderable = Status(
|
||||||
|
status,
|
||||||
|
console=self,
|
||||||
|
spinner=spinner,
|
||||||
|
spinner_style=spinner_style,
|
||||||
|
speed=speed,
|
||||||
|
refresh_per_second=refresh_per_second,
|
||||||
|
)
|
||||||
|
return status_renderable
|
||||||
|
|
||||||
def show_cursor(self, show: bool = True) -> None:
|
def show_cursor(self, show: bool = True) -> None:
|
||||||
"""Show or hide the cursor.
|
"""Show or hide the cursor.
|
||||||
|
|
||||||
|
@ -946,7 +981,7 @@ class Console:
|
||||||
highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default.
|
highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[ConsoleRenderable]: A list of things to render.
|
List[ConsoleRenderable]: A list oxf things to render.
|
||||||
"""
|
"""
|
||||||
renderables: List[ConsoleRenderable] = []
|
renderables: List[ConsoleRenderable] = []
|
||||||
_append = renderables.append
|
_append = renderables.append
|
||||||
|
@ -957,7 +992,7 @@ class Console:
|
||||||
if justify in ("left", "center", "right"):
|
if justify in ("left", "center", "right"):
|
||||||
|
|
||||||
def align_append(renderable: RenderableType) -> None:
|
def align_append(renderable: RenderableType) -> None:
|
||||||
_append(Align(renderable, cast(AlignValues, justify)))
|
_append(Align(renderable, cast(AlignMethod, justify)))
|
||||||
|
|
||||||
append = align_append
|
append = align_append
|
||||||
|
|
||||||
|
@ -967,7 +1002,7 @@ class Console:
|
||||||
|
|
||||||
def check_text() -> None:
|
def check_text() -> None:
|
||||||
if text:
|
if text:
|
||||||
sep_text = Text(sep, end=end)
|
sep_text = Text(sep, justify=justify, end=end)
|
||||||
append(sep_text.join(text))
|
append(sep_text.join(text))
|
||||||
del text[:]
|
del text[:]
|
||||||
|
|
||||||
|
@ -1003,16 +1038,19 @@ class Console:
|
||||||
*,
|
*,
|
||||||
characters: str = "─",
|
characters: str = "─",
|
||||||
style: Union[str, Style] = "rule.line",
|
style: Union[str, Style] = "rule.line",
|
||||||
|
align: AlignMethod = "center",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw a line with optional centered title.
|
"""Draw a line with optional centered title.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
title (str, optional): Text to render over the rule. Defaults to "".
|
title (str, optional): Text to render over the rule. Defaults to "".
|
||||||
characters (str, optional): Character(s) to form the line. Defaults to "─".
|
characters (str, optional): Character(s) to form the line. Defaults to "─".
|
||||||
|
style (str, optional): Style of line. Defaults to "rule.line".
|
||||||
|
align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
|
||||||
"""
|
"""
|
||||||
from .rule import Rule
|
from .rule import Rule
|
||||||
|
|
||||||
rule = Rule(title=title, characters=characters, style=style)
|
rule = Rule(title=title, characters=characters, style=style, align=align)
|
||||||
self.print(rule)
|
self.print(rule)
|
||||||
|
|
||||||
def control(self, control_codes: Union["Control", str]) -> None:
|
def control(self, control_codes: Union["Control", str]) -> None:
|
||||||
|
@ -1172,6 +1210,7 @@ class Console:
|
||||||
*objects: Any,
|
*objects: Any,
|
||||||
sep=" ",
|
sep=" ",
|
||||||
end="\n",
|
end="\n",
|
||||||
|
style: Union[str, Style] = None,
|
||||||
justify: JustifyMethod = None,
|
justify: JustifyMethod = None,
|
||||||
emoji: bool = None,
|
emoji: bool = None,
|
||||||
markup: bool = None,
|
markup: bool = None,
|
||||||
|
@ -1185,6 +1224,7 @@ class Console:
|
||||||
objects (positional args): Objects to log to the terminal.
|
objects (positional args): Objects to log to the terminal.
|
||||||
sep (str, optional): String to write between print data. Defaults to " ".
|
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".
|
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.
|
||||||
justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
|
justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``.
|
||||||
overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
|
overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None.
|
||||||
emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
|
emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None.
|
||||||
|
@ -1207,6 +1247,8 @@ class Console:
|
||||||
markup=markup,
|
markup=markup,
|
||||||
highlight=highlight,
|
highlight=highlight,
|
||||||
)
|
)
|
||||||
|
if style is not None:
|
||||||
|
renderables = [Styled(renderable, style) for renderable in renderables]
|
||||||
|
|
||||||
caller = inspect.stack()[_stack_offset]
|
caller = inspect.stack()[_stack_offset]
|
||||||
link_path = (
|
link_path = (
|
||||||
|
|
|
@ -8,8 +8,6 @@ from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .style import Style
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .console import (
|
from .console import (
|
||||||
Console,
|
Console,
|
||||||
|
|
|
@ -44,6 +44,7 @@ DEFAULT_STYLES: Dict[str, Style] = {
|
||||||
"inspect.equals": Style(),
|
"inspect.equals": Style(),
|
||||||
"inspect.help": Style(color="cyan"),
|
"inspect.help": Style(color="cyan"),
|
||||||
"inspect.doc": Style(dim=True),
|
"inspect.doc": Style(dim=True),
|
||||||
|
"live.ellipsis": Style(bold=True, color="red"),
|
||||||
"logging.keyword": Style(bold=True, color="yellow"),
|
"logging.keyword": Style(bold=True, color="yellow"),
|
||||||
"logging.level.notset": Style(dim=True),
|
"logging.level.notset": Style(dim=True),
|
||||||
"logging.level.debug": Style(color="green"),
|
"logging.level.debug": Style(color="green"),
|
||||||
|
@ -115,6 +116,8 @@ DEFAULT_STYLES: Dict[str, Style] = {
|
||||||
"progress.percentage": Style(color="magenta"),
|
"progress.percentage": Style(color="magenta"),
|
||||||
"progress.remaining": Style(color="cyan"),
|
"progress.remaining": Style(color="cyan"),
|
||||||
"progress.data.speed": Style(color="red"),
|
"progress.data.speed": Style(color="red"),
|
||||||
|
"progress.spinner": Style(color="green"),
|
||||||
|
"status.spinner": Style(color="green"),
|
||||||
}
|
}
|
||||||
|
|
||||||
MARKDOWN_STYLES = {
|
MARKDOWN_STYLES = {
|
||||||
|
|
47
rich/file_proxy.py
Normal file
47
rich/file_proxy.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import io
|
||||||
|
from typing import List, Any, IO, TYPE_CHECKING
|
||||||
|
|
||||||
|
from .ansi import AnsiDecoder
|
||||||
|
from .text import Text
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .console import Console
|
||||||
|
|
||||||
|
|
||||||
|
class FileProxy(io.TextIOBase):
|
||||||
|
"""Wraps a file (e.g. sys.stdout) and redirects writes to a console."""
|
||||||
|
|
||||||
|
def __init__(self, console: "Console", file: IO[str]) -> None:
|
||||||
|
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)
|
||||||
|
|
||||||
|
def write(self, text: str) -> int:
|
||||||
|
buffer = self.__buffer
|
||||||
|
lines: List[str] = []
|
||||||
|
while text:
|
||||||
|
line, new_line, text = text.partition("\n")
|
||||||
|
if new_line:
|
||||||
|
lines.append("".join(buffer) + line)
|
||||||
|
del buffer[:]
|
||||||
|
else:
|
||||||
|
buffer.append(line)
|
||||||
|
break
|
||||||
|
if lines:
|
||||||
|
console = self.__console
|
||||||
|
with console:
|
||||||
|
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)
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
buffer = self.__buffer
|
||||||
|
if buffer:
|
||||||
|
self.__console.print("".join(buffer))
|
||||||
|
del buffer[:]
|
|
@ -1,7 +1,6 @@
|
||||||
from typing import Iterable, List, TYPE_CHECKING
|
from typing import Iterable, List, TYPE_CHECKING
|
||||||
|
|
||||||
# from .console import Console as BaseConsole
|
from . import get_console
|
||||||
from .__init__ import get_console
|
|
||||||
from .segment import Segment
|
from .segment import Segment
|
||||||
from .terminal_theme import DEFAULT_TERMINAL_THEME
|
from .terminal_theme import DEFAULT_TERMINAL_THEME
|
||||||
|
|
||||||
|
|
376
rich/live.py
Normal file
376
rich/live.py
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
import sys
|
||||||
|
from threading import Event, RLock, Thread
|
||||||
|
from typing import IO, Any, List, Optional
|
||||||
|
|
||||||
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
from .__init__ import get_console
|
||||||
|
from ._loop import loop_last
|
||||||
|
from .console import (
|
||||||
|
Console,
|
||||||
|
ConsoleOptions,
|
||||||
|
ConsoleRenderable,
|
||||||
|
RenderableType,
|
||||||
|
RenderHook,
|
||||||
|
RenderResult,
|
||||||
|
)
|
||||||
|
from .control import Control
|
||||||
|
from .file_proxy import FileProxy
|
||||||
|
from .jupyter import JupyterMixin
|
||||||
|
from .live_render import LiveRender
|
||||||
|
from .segment import Segment
|
||||||
|
from .style import Style
|
||||||
|
from .text import Text
|
||||||
|
|
||||||
|
VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"]
|
||||||
|
|
||||||
|
|
||||||
|
class _RefreshThread(Thread):
|
||||||
|
"""A thread that calls refresh() at regular intervals."""
|
||||||
|
|
||||||
|
def __init__(self, live: "Live", refresh_per_second: float) -> None:
|
||||||
|
self.live = live
|
||||||
|
self.refresh_per_second = refresh_per_second
|
||||||
|
self.done = Event()
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
self.done.set()
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
while not self.done.wait(1 / self.refresh_per_second):
|
||||||
|
with self.live._lock:
|
||||||
|
self.live.refresh()
|
||||||
|
|
||||||
|
|
||||||
|
class _LiveRender(LiveRender):
|
||||||
|
def __init__(self, live: "Live", renderable: RenderableType) -> None:
|
||||||
|
self._live = live
|
||||||
|
self.renderable = renderable
|
||||||
|
self._shape: Optional[Tuple[int, int]] = None
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: Console, options: ConsoleOptions
|
||||||
|
) -> RenderResult:
|
||||||
|
with self._live._lock:
|
||||||
|
lines = console.render_lines(self.renderable, options, pad=False)
|
||||||
|
|
||||||
|
shape = Segment.get_shape(lines)
|
||||||
|
_, height = shape
|
||||||
|
if height > console.size.height:
|
||||||
|
if self._live.vertical_overflow == "crop":
|
||||||
|
lines = lines[: console.size.height]
|
||||||
|
shape = Segment.get_shape(lines)
|
||||||
|
elif self._live.vertical_overflow == "ellipsis":
|
||||||
|
lines = lines[: (console.size.height - 1)]
|
||||||
|
lines.append(
|
||||||
|
list(
|
||||||
|
console.render(
|
||||||
|
Text(
|
||||||
|
"...",
|
||||||
|
overflow="crop",
|
||||||
|
justify="center",
|
||||||
|
end="",
|
||||||
|
style="live.ellipsis",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
shape = Segment.get_shape(lines)
|
||||||
|
|
||||||
|
self._shape = shape
|
||||||
|
|
||||||
|
for last, line in loop_last(lines):
|
||||||
|
yield from line
|
||||||
|
if not last:
|
||||||
|
yield Segment.line()
|
||||||
|
|
||||||
|
|
||||||
|
class Live(JupyterMixin, RenderHook):
|
||||||
|
"""Renders an auto-updating live display of any given renderable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
renderable (RenderableType, optional): [The renderable to live display. Defaults to displaying nothing.
|
||||||
|
console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
|
||||||
|
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True
|
||||||
|
refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 1.
|
||||||
|
transient (bool, optional): Clear the renderable 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.
|
||||||
|
vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis".
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
renderable: RenderableType = "",
|
||||||
|
*,
|
||||||
|
console: Console = None,
|
||||||
|
auto_refresh: bool = True,
|
||||||
|
refresh_per_second: float = 4,
|
||||||
|
transient: bool = False,
|
||||||
|
redirect_stdout: bool = True,
|
||||||
|
redirect_stderr: bool = True,
|
||||||
|
vertical_overflow: VerticalOverflowMethod = "ellipsis",
|
||||||
|
) -> None:
|
||||||
|
assert refresh_per_second > 0, "refresh_per_second must be > 0"
|
||||||
|
self.console = console if console is not None else get_console()
|
||||||
|
self._live_render = _LiveRender(self, renderable)
|
||||||
|
|
||||||
|
self._redirect_stdout = redirect_stdout
|
||||||
|
self._redirect_stderr = redirect_stderr
|
||||||
|
self._restore_stdout: Optional[IO[str]] = None
|
||||||
|
self._restore_stderr: Optional[IO[str]] = None
|
||||||
|
|
||||||
|
self._lock = RLock()
|
||||||
|
self.ipy_widget: Optional[Any] = None
|
||||||
|
self.auto_refresh = auto_refresh
|
||||||
|
self._started: bool = False
|
||||||
|
self.transient = transient
|
||||||
|
|
||||||
|
self._refresh_thread: Optional[_RefreshThread] = None
|
||||||
|
self.refresh_per_second = refresh_per_second
|
||||||
|
|
||||||
|
self.vertical_overflow = vertical_overflow
|
||||||
|
# cant store just clear_control as the live_render shape is lazily computed on render
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
"""Start live rendering display."""
|
||||||
|
with self._lock:
|
||||||
|
if self._started:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.console.show_cursor(False)
|
||||||
|
self._enable_redirect_io()
|
||||||
|
self.console.push_render_hook(self)
|
||||||
|
self._started = True
|
||||||
|
|
||||||
|
if self.auto_refresh:
|
||||||
|
self._refresh_thread = _RefreshThread(self, self.refresh_per_second)
|
||||||
|
self._refresh_thread.start()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Stop live rendering display."""
|
||||||
|
with self._lock:
|
||||||
|
if not self._started:
|
||||||
|
return
|
||||||
|
self._started = False
|
||||||
|
try:
|
||||||
|
if self.auto_refresh and self._refresh_thread is not None:
|
||||||
|
self._refresh_thread.stop()
|
||||||
|
# allow it to fully render on the last even if overflow
|
||||||
|
self.vertical_overflow = "visible"
|
||||||
|
self.refresh()
|
||||||
|
if self.console.is_terminal:
|
||||||
|
self.console.line()
|
||||||
|
finally:
|
||||||
|
self._disable_redirect_io()
|
||||||
|
self.console.pop_render_hook()
|
||||||
|
self.console.show_cursor(True)
|
||||||
|
|
||||||
|
if self.transient:
|
||||||
|
self.console.control(self._live_render.restore_cursor())
|
||||||
|
if self.ipy_widget is not None and self.transient: # pragma: no cover
|
||||||
|
self.ipy_widget.clear_output()
|
||||||
|
self.ipy_widget.close()
|
||||||
|
|
||||||
|
def __enter__(self) -> "Live":
|
||||||
|
self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def _enable_redirect_io(self):
|
||||||
|
"""Enable redirecting of stdout / stderr."""
|
||||||
|
if self.console.is_terminal:
|
||||||
|
if self._redirect_stdout:
|
||||||
|
self._restore_stdout = sys.stdout
|
||||||
|
sys.stdout = FileProxy(self.console, sys.stdout)
|
||||||
|
if self._redirect_stderr:
|
||||||
|
self._restore_stderr = sys.stderr
|
||||||
|
sys.stderr = FileProxy(self.console, sys.stderr)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def renderable(self) -> RenderableType:
|
||||||
|
"""Get the renderable that is being displayed
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RenderableType: Displayed renderable.
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
return self._live_render.renderable
|
||||||
|
|
||||||
|
def update(self, renderable: RenderableType, *, refresh: bool = False) -> None:
|
||||||
|
"""Update the renderable that is being displayed
|
||||||
|
|
||||||
|
Args:
|
||||||
|
renderable (RenderableType): New renderable to use.
|
||||||
|
refresh (bool, optional): Refresh the display. Defaults to False.
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
self._live_render.set_renderable(renderable)
|
||||||
|
if refresh:
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
"""Update the display of the Live Render."""
|
||||||
|
if self.console.is_jupyter: # pragma: no cover
|
||||||
|
try:
|
||||||
|
from IPython.display import display
|
||||||
|
from ipywidgets import Output
|
||||||
|
except ImportError:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.warn('install "ipywidgets" for Jupyter support')
|
||||||
|
else:
|
||||||
|
with self._lock:
|
||||||
|
if self.ipy_widget is None:
|
||||||
|
self.ipy_widget = Output()
|
||||||
|
display(self.ipy_widget)
|
||||||
|
|
||||||
|
with self.ipy_widget:
|
||||||
|
self.ipy_widget.clear_output(wait=True)
|
||||||
|
self.console.print(self._live_render.renderable)
|
||||||
|
elif self.console.is_terminal and not self.console.is_dumb_terminal:
|
||||||
|
with self._lock, self.console:
|
||||||
|
self.console.print(Control(""))
|
||||||
|
elif (
|
||||||
|
not self._started and not self.transient
|
||||||
|
): # if it is finished allow files or dumb-terminals to see final result
|
||||||
|
with self.console:
|
||||||
|
self.console.print(Control(""))
|
||||||
|
|
||||||
|
def _disable_redirect_io(self):
|
||||||
|
"""Disable redirecting of stdout / stderr."""
|
||||||
|
if self._restore_stdout:
|
||||||
|
sys.stdout = self._restore_stdout
|
||||||
|
self._restore_stdout = None
|
||||||
|
if self._restore_stderr:
|
||||||
|
sys.stderr = self._restore_stderr
|
||||||
|
self._restore_stderr = None
|
||||||
|
|
||||||
|
def process_renderables(
|
||||||
|
self, renderables: List[ConsoleRenderable]
|
||||||
|
) -> List[ConsoleRenderable]:
|
||||||
|
"""Process renderables to restore cursor and display progress."""
|
||||||
|
if self.console.is_terminal:
|
||||||
|
# lock needs acquiring as user can modify live_render renerable at any time unlike in Progress.
|
||||||
|
with self._lock:
|
||||||
|
# determine the control command needed to clear previous rendering
|
||||||
|
renderables = [
|
||||||
|
self._live_render.position_cursor(),
|
||||||
|
*renderables,
|
||||||
|
self._live_render,
|
||||||
|
]
|
||||||
|
elif (
|
||||||
|
not self._started and not self.transient
|
||||||
|
): # if it is finished render the final output for files or dumb_terminals
|
||||||
|
renderables = [*renderables, self._live_render]
|
||||||
|
|
||||||
|
return renderables
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from itertools import cycle
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
from .console import Console
|
||||||
|
from .live import Live
|
||||||
|
from .panel import Panel
|
||||||
|
from .rule import Rule
|
||||||
|
from .syntax import Syntax
|
||||||
|
from .table import Table
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
syntax = Syntax(
|
||||||
|
'''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]:
|
||||||
|
"""Iterate and generate a tuple with a flag for last value."""
|
||||||
|
iter_values = iter(values)
|
||||||
|
try:
|
||||||
|
previous_value = next(iter_values)
|
||||||
|
except StopIteration:
|
||||||
|
return
|
||||||
|
for value in iter_values:
|
||||||
|
yield False, previous_value
|
||||||
|
previous_value = value
|
||||||
|
yield True, previous_value''',
|
||||||
|
"python",
|
||||||
|
line_numbers=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
table = Table("foo", "bar", "baz")
|
||||||
|
table.add_row("1", "2", "3")
|
||||||
|
|
||||||
|
progress_renderables = [
|
||||||
|
"You can make the terminal shorter and taller to see the live table hide"
|
||||||
|
"Text may be printed while the progress bars are rendering.",
|
||||||
|
Panel("In fact, [i]any[/i] renderable will work"),
|
||||||
|
"Such as [magenta]tables[/]...",
|
||||||
|
table,
|
||||||
|
"Pretty printed structures...",
|
||||||
|
{"type": "example", "text": "Pretty printed"},
|
||||||
|
"Syntax...",
|
||||||
|
syntax,
|
||||||
|
Rule("Give it a try!"),
|
||||||
|
]
|
||||||
|
|
||||||
|
examples = cycle(progress_renderables)
|
||||||
|
|
||||||
|
exchanges = [
|
||||||
|
"SGD",
|
||||||
|
"MYR",
|
||||||
|
"EUR",
|
||||||
|
"USD",
|
||||||
|
"AUD",
|
||||||
|
"JPY",
|
||||||
|
"CNH",
|
||||||
|
"HKD",
|
||||||
|
"CAD",
|
||||||
|
"INR",
|
||||||
|
"DKK",
|
||||||
|
"GBP",
|
||||||
|
"RUB",
|
||||||
|
"NZD",
|
||||||
|
"MXN",
|
||||||
|
"IDR",
|
||||||
|
"TWD",
|
||||||
|
"THB",
|
||||||
|
"VND",
|
||||||
|
]
|
||||||
|
with Live(console=console) as live_table:
|
||||||
|
exchange_rate_dict: Dict[Tuple[str, str], float] = {}
|
||||||
|
|
||||||
|
for index in range(100):
|
||||||
|
select_exchange = exchanges[index % len(exchanges)]
|
||||||
|
|
||||||
|
for exchange in exchanges:
|
||||||
|
if exchange == select_exchange:
|
||||||
|
continue
|
||||||
|
time.sleep(0.4)
|
||||||
|
if random.randint(0, 10) < 1:
|
||||||
|
console.log(next(examples))
|
||||||
|
exchange_rate_dict[(select_exchange, exchange)] = 200 / (
|
||||||
|
(random.random() * 320) + 1
|
||||||
|
)
|
||||||
|
if len(exchange_rate_dict) > len(exchanges) - 1:
|
||||||
|
exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0])
|
||||||
|
table = Table(title="Exchange Rates")
|
||||||
|
|
||||||
|
table.add_column("Source Currency")
|
||||||
|
table.add_column("Destination Currency")
|
||||||
|
table.add_column("Exchange Rate")
|
||||||
|
|
||||||
|
for ((soure, dest), exchange_rate) in exchange_rate_dict.items():
|
||||||
|
table.add_row(
|
||||||
|
soure,
|
||||||
|
dest,
|
||||||
|
Text(
|
||||||
|
f"{exchange_rate:.4f}",
|
||||||
|
style="red" if exchange_rate < 1.0 else "green",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
live_table.update(table)
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from logging import Handler, LogRecord
|
from logging import Handler, LogRecord
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import ClassVar, List, Optional, Type
|
from typing import ClassVar, List, Optional, Type, Union
|
||||||
|
|
||||||
from . import get_console
|
from . import get_console
|
||||||
from ._log_render import LogRender
|
from ._log_render import LogRender
|
||||||
|
@ -21,7 +21,7 @@ class RichHandler(Handler):
|
||||||
under your control. If a dependency writes messages containing square brackets, it may not produce the intended output.
|
under your control. If a dependency writes messages containing square brackets, it may not produce the intended output.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
level (int, optional): Log level. Defaults to logging.NOTSET.
|
level (Union[int, str], optional): Log level. Defaults to logging.NOTSET.
|
||||||
console (:class:`~rich.console.Console`, optional): Optional console instance to write logs.
|
console (:class:`~rich.console.Console`, optional): Optional console instance to write logs.
|
||||||
Default will use a global console instance writing to stdout.
|
Default will use a global console instance writing to stdout.
|
||||||
show_time (bool, optional): Show a column for the time. Defaults to True.
|
show_time (bool, optional): Show a column for the time. Defaults to True.
|
||||||
|
@ -36,6 +36,9 @@ class RichHandler(Handler):
|
||||||
tracebacks_theme (str, optional): Override pygments theme used in traceback.
|
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_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.
|
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]]] = [
|
KEYWORDS: ClassVar[Optional[List[str]]] = [
|
||||||
|
@ -52,7 +55,7 @@ class RichHandler(Handler):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
level: int = logging.NOTSET,
|
level: Union[int, str] = logging.NOTSET,
|
||||||
console: Console = None,
|
console: Console = None,
|
||||||
*,
|
*,
|
||||||
show_time: bool = True,
|
show_time: bool = True,
|
||||||
|
@ -67,6 +70,8 @@ class RichHandler(Handler):
|
||||||
tracebacks_theme: Optional[str] = None,
|
tracebacks_theme: Optional[str] = None,
|
||||||
tracebacks_word_wrap: bool = True,
|
tracebacks_word_wrap: bool = True,
|
||||||
tracebacks_show_locals: bool = False,
|
tracebacks_show_locals: bool = False,
|
||||||
|
locals_max_length: int = 10,
|
||||||
|
locals_max_string: int = 80,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(level=level)
|
super().__init__(level=level)
|
||||||
self.console = console or get_console()
|
self.console = console or get_console()
|
||||||
|
@ -85,6 +90,8 @@ class RichHandler(Handler):
|
||||||
self.tracebacks_theme = tracebacks_theme
|
self.tracebacks_theme = tracebacks_theme
|
||||||
self.tracebacks_word_wrap = tracebacks_word_wrap
|
self.tracebacks_word_wrap = tracebacks_word_wrap
|
||||||
self.tracebacks_show_locals = tracebacks_show_locals
|
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:
|
def get_level_text(self, record: LogRecord) -> Text:
|
||||||
"""Get the level name from the record.
|
"""Get the level name from the record.
|
||||||
|
@ -127,6 +134,8 @@ class RichHandler(Handler):
|
||||||
theme=self.tracebacks_theme,
|
theme=self.tracebacks_theme,
|
||||||
word_wrap=self.tracebacks_word_wrap,
|
word_wrap=self.tracebacks_word_wrap,
|
||||||
show_locals=self.tracebacks_show_locals,
|
show_locals=self.tracebacks_show_locals,
|
||||||
|
locals_max_length=self.locals_max_length,
|
||||||
|
locals_max_string=self.locals_max_string,
|
||||||
)
|
)
|
||||||
message = record.getMessage()
|
message = record.getMessage()
|
||||||
|
|
||||||
|
@ -201,6 +210,8 @@ if __name__ == "__main__": # pragma: no cover
|
||||||
def divide():
|
def divide():
|
||||||
number = 1
|
number = 1
|
||||||
divisor = 0
|
divisor = 0
|
||||||
|
foos = ["foo"] * 100
|
||||||
|
log.debug("in divide")
|
||||||
try:
|
try:
|
||||||
number / divisor
|
number / divisor
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from .box import Box, ROUNDED
|
from .box import Box, ROUNDED
|
||||||
|
|
||||||
from .align import AlignValues
|
from .align import AlignMethod
|
||||||
from .jupyter import JupyterMixin
|
from .jupyter import JupyterMixin
|
||||||
from .measure import Measurement, measure_renderables
|
from .measure import Measurement, measure_renderables
|
||||||
from .padding import Padding, PaddingDimensions
|
from .padding import Padding, PaddingDimensions
|
||||||
|
@ -39,7 +39,7 @@ class Panel(JupyterMixin):
|
||||||
box: Box = ROUNDED,
|
box: Box = ROUNDED,
|
||||||
*,
|
*,
|
||||||
title: TextType = None,
|
title: TextType = None,
|
||||||
title_align: AlignValues = "center",
|
title_align: AlignMethod = "center",
|
||||||
safe_box: Optional[bool] = None,
|
safe_box: Optional[bool] = None,
|
||||||
expand: bool = True,
|
expand: bool = True,
|
||||||
style: StyleType = "none",
|
style: StyleType = "none",
|
||||||
|
@ -65,7 +65,7 @@ class Panel(JupyterMixin):
|
||||||
box: Box = ROUNDED,
|
box: Box = ROUNDED,
|
||||||
*,
|
*,
|
||||||
title: TextType = None,
|
title: TextType = None,
|
||||||
title_align: AlignValues = "center",
|
title_align: AlignMethod = "center",
|
||||||
safe_box: Optional[bool] = None,
|
safe_box: Optional[bool] = None,
|
||||||
style: StyleType = "none",
|
style: StyleType = "none",
|
||||||
border_style: StyleType = "none",
|
border_style: StyleType = "none",
|
||||||
|
|
|
@ -20,7 +20,7 @@ from typing import (
|
||||||
from rich.highlighter import ReprHighlighter
|
from rich.highlighter import ReprHighlighter
|
||||||
|
|
||||||
from .abc import RichRenderable
|
from .abc import RichRenderable
|
||||||
from .__init__ import get_console
|
from . import get_console
|
||||||
from ._pick import pick_bool
|
from ._pick import pick_bool
|
||||||
from .cells import cell_len
|
from .cells import cell_len
|
||||||
from .highlighter import ReprHighlighter
|
from .highlighter import ReprHighlighter
|
||||||
|
@ -44,6 +44,7 @@ def install(
|
||||||
crop: bool = False,
|
crop: bool = False,
|
||||||
indent_guides: bool = False,
|
indent_guides: bool = False,
|
||||||
max_length: int = None,
|
max_length: int = None,
|
||||||
|
max_string: int = None,
|
||||||
expand_all: bool = False,
|
expand_all: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Install automatic pretty printing in the Python REPL.
|
"""Install automatic pretty printing in the Python REPL.
|
||||||
|
@ -55,6 +56,7 @@ def install(
|
||||||
indent_guides (bool, optional): Enable indentation guides. Defaults to False.
|
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.
|
max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
|
||||||
Defaults to None.
|
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
|
expand_all (bool, optional): Expand all containers. Defaults to False
|
||||||
"""
|
"""
|
||||||
from rich import get_console
|
from rich import get_console
|
||||||
|
@ -74,6 +76,7 @@ def install(
|
||||||
overflow=overflow,
|
overflow=overflow,
|
||||||
indent_guides=indent_guides,
|
indent_guides=indent_guides,
|
||||||
max_length=max_length,
|
max_length=max_length,
|
||||||
|
max_string=max_string,
|
||||||
expand_all=expand_all,
|
expand_all=expand_all,
|
||||||
),
|
),
|
||||||
crop=crop,
|
crop=crop,
|
||||||
|
@ -152,7 +155,11 @@ class Pretty:
|
||||||
|
|
||||||
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
def __rich_measure__(self, console: "Console", max_width: int) -> "Measurement":
|
||||||
pretty_str = pretty_repr(
|
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())
|
text_width = max(cell_len(line) for line in pretty_str.splitlines())
|
||||||
return Measurement(text_width, text_width)
|
return Measurement(text_width, text_width)
|
||||||
|
|
140
rich/progress.py
140
rich/progress.py
|
@ -1,4 +1,3 @@
|
||||||
import io
|
|
||||||
import sys
|
import sys
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
@ -7,10 +6,8 @@ from dataclasses import dataclass, field
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from threading import Event, RLock, Thread
|
from threading import Event, RLock, Thread
|
||||||
from time import monotonic
|
|
||||||
from typing import (
|
from typing import (
|
||||||
IO,
|
IO,
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Deque,
|
Deque,
|
||||||
|
@ -27,7 +24,6 @@ from typing import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import filesize, get_console
|
from . import filesize, get_console
|
||||||
from .ansi import AnsiDecoder
|
|
||||||
from .console import (
|
from .console import (
|
||||||
Console,
|
Console,
|
||||||
ConsoleRenderable,
|
ConsoleRenderable,
|
||||||
|
@ -37,13 +33,15 @@ from .console import (
|
||||||
RenderHook,
|
RenderHook,
|
||||||
)
|
)
|
||||||
from .control import Control
|
from .control import Control
|
||||||
|
from .file_proxy import FileProxy
|
||||||
from .highlighter import Highlighter
|
from .highlighter import Highlighter
|
||||||
from .jupyter import JupyterMixin
|
from .jupyter import JupyterMixin
|
||||||
from .live_render import LiveRender
|
from .live_render import LiveRender
|
||||||
from .progress_bar import ProgressBar
|
from .progress_bar import ProgressBar
|
||||||
|
from .spinner import Spinner
|
||||||
from .style import StyleType
|
from .style import StyleType
|
||||||
from .table import Table
|
from .table import Table
|
||||||
from .text import Text
|
from .text import Text, TextType
|
||||||
|
|
||||||
TaskID = NewType("TaskID", int)
|
TaskID = NewType("TaskID", int)
|
||||||
|
|
||||||
|
@ -95,12 +93,13 @@ def track(
|
||||||
console: Optional[Console] = None,
|
console: Optional[Console] = None,
|
||||||
transient: bool = False,
|
transient: bool = False,
|
||||||
get_time: Callable[[], float] = None,
|
get_time: Callable[[], float] = None,
|
||||||
refresh_per_second: int = None,
|
refresh_per_second: float = None,
|
||||||
style: StyleType = "bar.back",
|
style: StyleType = "bar.back",
|
||||||
complete_style: StyleType = "bar.complete",
|
complete_style: StyleType = "bar.complete",
|
||||||
finished_style: StyleType = "bar.finished",
|
finished_style: StyleType = "bar.finished",
|
||||||
pulse_style: StyleType = "bar.pulse",
|
pulse_style: StyleType = "bar.pulse",
|
||||||
update_period: float = 0.1,
|
update_period: float = 0.1,
|
||||||
|
disable: bool = False,
|
||||||
) -> Iterable[ProgressType]:
|
) -> Iterable[ProgressType]:
|
||||||
"""Track progress by iterating over a sequence.
|
"""Track progress by iterating over a sequence.
|
||||||
|
|
||||||
|
@ -111,12 +110,13 @@ def track(
|
||||||
auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
|
auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True.
|
||||||
transient: (bool, optional): Clear the progress on exit. Defaults to False.
|
transient: (bool, optional): Clear the progress on exit. Defaults to False.
|
||||||
console (Console, optional): Console to write to. Default creates internal Console instance.
|
console (Console, optional): Console to write to. Default creates internal Console instance.
|
||||||
refresh_per_second (Optional[int], optional): Number of times per second to refresh the progress information, or None to use default. Defaults to None.
|
refresh_per_second (Optional[float], optional): Number of times per second to refresh the progress information, or None to use default. Defaults to None.
|
||||||
style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
|
style (StyleType, optional): Style for the bar background. Defaults to "bar.back".
|
||||||
complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
|
complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete".
|
||||||
finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
|
finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.done".
|
||||||
pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
|
pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse".
|
||||||
update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1.
|
update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1.
|
||||||
|
disable (bool, optional): Disable display of progress.
|
||||||
Returns:
|
Returns:
|
||||||
Iterable[ProgressType]: An iterable of the values in the sequence.
|
Iterable[ProgressType]: An iterable of the values in the sequence.
|
||||||
|
|
||||||
|
@ -144,6 +144,7 @@ def track(
|
||||||
transient=transient,
|
transient=transient,
|
||||||
get_time=get_time,
|
get_time=get_time,
|
||||||
refresh_per_second=refresh_per_second,
|
refresh_per_second=refresh_per_second,
|
||||||
|
disable=disable,
|
||||||
)
|
)
|
||||||
|
|
||||||
with progress:
|
with progress:
|
||||||
|
@ -189,6 +190,68 @@ class ProgressColumn(ABC):
|
||||||
"""Should return a renderable object."""
|
"""Should return a renderable object."""
|
||||||
|
|
||||||
|
|
||||||
|
class RenderableColumn(ProgressColumn):
|
||||||
|
"""A column to insert an arbitrary column.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
renderable (RenderableType, optional): Any renderable. Defaults to empty string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, renderable: RenderableType = ""):
|
||||||
|
self.renderable = renderable
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def render(self, task: "Task") -> RenderableType:
|
||||||
|
return self.renderable
|
||||||
|
|
||||||
|
|
||||||
|
class SpinnerColumn(ProgressColumn):
|
||||||
|
"""A column with a 'spinner' animation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spinner_name (str, optional): Name of spinner animation. Defaults to "dots".
|
||||||
|
style (StyleType, optional): Style of spinner. Defaults to "progress.spinner".
|
||||||
|
speed (float, optional): Speed factor of spinner. Defaults to 1.0.
|
||||||
|
finished_text (TextType, optional): Text used when task is finished. Defaults to " ".
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
spinner_name: str = "dots",
|
||||||
|
style: Optional[StyleType] = "progress.spinner",
|
||||||
|
speed: float = 1.0,
|
||||||
|
finished_text: TextType = " ",
|
||||||
|
):
|
||||||
|
self.spinner = Spinner(spinner_name, style=style, speed=speed)
|
||||||
|
self.finished_text = (
|
||||||
|
Text.from_markup(finished_text)
|
||||||
|
if isinstance(finished_text, str)
|
||||||
|
else finished_text
|
||||||
|
)
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def set_spinner(
|
||||||
|
self,
|
||||||
|
spinner_name: str,
|
||||||
|
spinner_style: Optional[StyleType] = "progress.spinner",
|
||||||
|
speed: float = 1.0,
|
||||||
|
):
|
||||||
|
"""Set a new spinner.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spinner_name (str): Spinner name, see python -m rich.spinner.
|
||||||
|
spinner_style (Optional[StyleType], optional): Spinner style. Defaults to "progress.spinner".
|
||||||
|
speed (float, optional): Speed factor of spinner. Defaults to 1.0.
|
||||||
|
"""
|
||||||
|
self.spinner = Spinner(spinner_name, style=spinner_style, speed=speed)
|
||||||
|
|
||||||
|
def render(self, task: "Task") -> Text:
|
||||||
|
if task.finished:
|
||||||
|
return self.finished_text
|
||||||
|
text = self.spinner.render(task.get_time())
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
class TextColumn(ProgressColumn):
|
class TextColumn(ProgressColumn):
|
||||||
"""A column containing text."""
|
"""A column containing text."""
|
||||||
|
|
||||||
|
@ -460,7 +523,7 @@ class Task:
|
||||||
class _RefreshThread(Thread):
|
class _RefreshThread(Thread):
|
||||||
"""A thread that calls refresh() on the Process object at regular intervals."""
|
"""A thread that calls refresh() on the Process object at regular intervals."""
|
||||||
|
|
||||||
def __init__(self, progress: "Progress", refresh_per_second: int = 10) -> None:
|
def __init__(self, progress: "Progress", refresh_per_second: float = 10) -> None:
|
||||||
self.progress = progress
|
self.progress = progress
|
||||||
self.refresh_per_second = refresh_per_second
|
self.refresh_per_second = refresh_per_second
|
||||||
self.done = Event()
|
self.done = Event()
|
||||||
|
@ -474,57 +537,19 @@ class _RefreshThread(Thread):
|
||||||
self.progress.refresh()
|
self.progress.refresh()
|
||||||
|
|
||||||
|
|
||||||
class _FileProxy(io.TextIOBase):
|
|
||||||
"""Wraps a file (e.g. sys.stdout) and redirects writes to a console."""
|
|
||||||
|
|
||||||
def __init__(self, console: Console, file: IO[str]) -> None:
|
|
||||||
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)
|
|
||||||
|
|
||||||
def write(self, text: str) -> int:
|
|
||||||
buffer = self.__buffer
|
|
||||||
lines: List[str] = []
|
|
||||||
while text:
|
|
||||||
line, new_line, text = text.partition("\n")
|
|
||||||
if new_line:
|
|
||||||
lines.append("".join(buffer) + line)
|
|
||||||
del buffer[:]
|
|
||||||
else:
|
|
||||||
buffer.append(line)
|
|
||||||
break
|
|
||||||
if lines:
|
|
||||||
console = self.__console
|
|
||||||
with console:
|
|
||||||
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)
|
|
||||||
|
|
||||||
def flush(self) -> None:
|
|
||||||
buffer = self.__buffer
|
|
||||||
if buffer:
|
|
||||||
self.__console.print("".join(buffer))
|
|
||||||
del buffer[:]
|
|
||||||
|
|
||||||
|
|
||||||
class Progress(JupyterMixin, RenderHook):
|
class Progress(JupyterMixin, RenderHook):
|
||||||
"""Renders an auto-updating progress bar(s).
|
"""Renders an auto-updating progress bar(s).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
|
console (Console, optional): Optional Console instance. Default will an internal Console instance writing to stdout.
|
||||||
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`.
|
auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`.
|
||||||
refresh_per_second (Optional[int], optional): Number of times per second to refresh the progress information or None to use default (10). Defaults to None.
|
refresh_per_second (Optional[float], optional): Number of times per second to refresh the progress information or None to use default (10). Defaults to None.
|
||||||
speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30.
|
speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30.
|
||||||
transient: (bool, optional): Clear the progress on exit. Defaults to False.
|
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_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.
|
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 Console.get_time. 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__(
|
def __init__(
|
||||||
|
@ -532,12 +557,13 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
*columns: Union[str, ProgressColumn],
|
*columns: Union[str, ProgressColumn],
|
||||||
console: Console = None,
|
console: Console = None,
|
||||||
auto_refresh: bool = True,
|
auto_refresh: bool = True,
|
||||||
refresh_per_second: int = None,
|
refresh_per_second: float = None,
|
||||||
speed_estimate_period: float = 30.0,
|
speed_estimate_period: float = 30.0,
|
||||||
transient: bool = False,
|
transient: bool = False,
|
||||||
redirect_stdout: bool = True,
|
redirect_stdout: bool = True,
|
||||||
redirect_stderr: bool = True,
|
redirect_stderr: bool = True,
|
||||||
get_time: GetTimeCallable = None,
|
get_time: GetTimeCallable = None,
|
||||||
|
disable: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert (
|
assert (
|
||||||
refresh_per_second is None or refresh_per_second > 0
|
refresh_per_second is None or refresh_per_second > 0
|
||||||
|
@ -557,6 +583,7 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
self._redirect_stdout = redirect_stdout
|
self._redirect_stdout = redirect_stdout
|
||||||
self._redirect_stderr = redirect_stderr
|
self._redirect_stderr = redirect_stderr
|
||||||
self.get_time = get_time or self.console.get_time
|
self.get_time = get_time or self.console.get_time
|
||||||
|
self.disable = disable
|
||||||
self._tasks: Dict[TaskID, Task] = {}
|
self._tasks: Dict[TaskID, Task] = {}
|
||||||
self._live_render = LiveRender(self.get_renderable())
|
self._live_render = LiveRender(self.get_renderable())
|
||||||
self._task_index: TaskID = TaskID(0)
|
self._task_index: TaskID = TaskID(0)
|
||||||
|
@ -593,10 +620,10 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
if self.console.is_terminal:
|
if self.console.is_terminal:
|
||||||
if self._redirect_stdout:
|
if self._redirect_stdout:
|
||||||
self._restore_stdout = sys.stdout
|
self._restore_stdout = sys.stdout
|
||||||
sys.stdout = _FileProxy(self.console, sys.stdout)
|
sys.stdout = FileProxy(self.console, sys.stdout)
|
||||||
if self._redirect_stderr:
|
if self._redirect_stderr:
|
||||||
self._restore_stderr = sys.stderr
|
self._restore_stderr = sys.stderr
|
||||||
sys.stderr = _FileProxy(self.console, sys.stderr)
|
sys.stderr = FileProxy(self.console, sys.stderr)
|
||||||
|
|
||||||
def _disable_redirect_io(self):
|
def _disable_redirect_io(self):
|
||||||
"""Disable redirecting of stdout / stderr."""
|
"""Disable redirecting of stdout / stderr."""
|
||||||
|
@ -848,6 +875,7 @@ class Progress(JupyterMixin, RenderHook):
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
"""Refresh (render) the progress information."""
|
"""Refresh (render) the progress information."""
|
||||||
|
if not self.disable:
|
||||||
if self.console.is_jupyter: # pragma: no cover
|
if self.console.is_jupyter: # pragma: no cover
|
||||||
try:
|
try:
|
||||||
from IPython.display import display
|
from IPython.display import display
|
||||||
|
@ -1023,7 +1051,15 @@ if __name__ == "__main__": # pragma: no coverage
|
||||||
|
|
||||||
console = Console(record=True)
|
console = Console(record=True)
|
||||||
try:
|
try:
|
||||||
with Progress(console=console, transient=True) as progress:
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn("[progress.description]{task.description}"),
|
||||||
|
BarColumn(),
|
||||||
|
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
|
||||||
|
TimeRemainingColumn(),
|
||||||
|
console=console,
|
||||||
|
transient=True,
|
||||||
|
) as progress:
|
||||||
|
|
||||||
task1 = progress.add_task("[red]Downloading", total=1000)
|
task1 = progress.add_task("[red]Downloading", total=1000)
|
||||||
task2 = progress.add_task("[green]Processing", total=1000)
|
task2 = progress.add_task("[green]Processing", total=1000)
|
||||||
|
|
|
@ -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 .console import Console
|
||||||
from .text import Text, TextType
|
from .text import Text, TextType
|
||||||
|
|
||||||
|
|
30
rich/rule.py
30
rich/rule.py
|
@ -1,5 +1,6 @@
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
|
from .align import AlignMethod
|
||||||
from .cells import cell_len, set_cell_size
|
from .cells import cell_len, set_cell_size
|
||||||
from .console import Console, ConsoleOptions, RenderResult
|
from .console import Console, ConsoleOptions, RenderResult
|
||||||
from .jupyter import JupyterMixin
|
from .jupyter import JupyterMixin
|
||||||
|
@ -15,6 +16,7 @@ class Rule(JupyterMixin):
|
||||||
characters (str, optional): Character(s) used to draw the line. Defaults to "─".
|
characters (str, optional): Character(s) used to draw the line. Defaults to "─".
|
||||||
style (StyleType, optional): Style of Rule. Defaults to "rule.line".
|
style (StyleType, optional): Style of Rule. Defaults to "rule.line".
|
||||||
end (str, optional): Character at end of Rule. defaults to "\\n"
|
end (str, optional): Character at end of Rule. defaults to "\\n"
|
||||||
|
align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -24,15 +26,21 @@ class Rule(JupyterMixin):
|
||||||
characters: str = "─",
|
characters: str = "─",
|
||||||
style: Union[str, Style] = "rule.line",
|
style: Union[str, Style] = "rule.line",
|
||||||
end: str = "\n",
|
end: str = "\n",
|
||||||
|
align: AlignMethod = "center",
|
||||||
) -> None:
|
) -> None:
|
||||||
if cell_len(characters) < 1:
|
if cell_len(characters) < 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"'characters' argument must have a cell width of at least 1"
|
"'characters' argument must have a cell width of at least 1"
|
||||||
)
|
)
|
||||||
|
if align not in ("left", "center", "right"):
|
||||||
|
raise ValueError(
|
||||||
|
f'invalid value for align, expected "left", "center", "right" (not {align!r})'
|
||||||
|
)
|
||||||
self.title = title
|
self.title = title
|
||||||
self.characters = characters
|
self.characters = characters
|
||||||
self.style = style
|
self.style = style
|
||||||
self.end = end
|
self.end = end
|
||||||
|
self.align = align
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Rule({self.title!r}, {self.characters!r})"
|
return f"Rule({self.title!r}, {self.characters!r})"
|
||||||
|
@ -56,18 +64,21 @@ class Rule(JupyterMixin):
|
||||||
if not self.title:
|
if not self.title:
|
||||||
rule_text = Text(characters * ((width // chars_len) + 1), self.style)
|
rule_text = Text(characters * ((width // chars_len) + 1), self.style)
|
||||||
rule_text.truncate(width)
|
rule_text.truncate(width)
|
||||||
else:
|
rule_text.plain = set_cell_size(rule_text.plain, width)
|
||||||
|
yield rule_text
|
||||||
|
return
|
||||||
|
|
||||||
if isinstance(self.title, Text):
|
if isinstance(self.title, Text):
|
||||||
title_text = self.title
|
title_text = self.title
|
||||||
else:
|
else:
|
||||||
title_text = console.render_str(self.title, style="rule.text")
|
title_text = console.render_str(self.title, style="rule.text")
|
||||||
|
|
||||||
if cell_len(title_text.plain) > width - 4:
|
|
||||||
title_text.truncate(width - 4, overflow="ellipsis")
|
|
||||||
|
|
||||||
title_text.plain = title_text.plain.replace("\n", " ")
|
title_text.plain = title_text.plain.replace("\n", " ")
|
||||||
title_text.expand_tabs()
|
title_text.expand_tabs()
|
||||||
rule_text = Text(end=self.end)
|
rule_text = Text(end=self.end)
|
||||||
|
|
||||||
|
if self.align == "center":
|
||||||
|
title_text.truncate(width - 4, overflow="ellipsis")
|
||||||
side_width = (width - cell_len(title_text.plain)) // 2
|
side_width = (width - cell_len(title_text.plain)) // 2
|
||||||
left = Text(characters * (side_width // chars_len + 1))
|
left = Text(characters * (side_width // chars_len + 1))
|
||||||
left.truncate(side_width - 1)
|
left.truncate(side_width - 1)
|
||||||
|
@ -77,6 +88,17 @@ class Rule(JupyterMixin):
|
||||||
rule_text.append(left.plain + " ", self.style)
|
rule_text.append(left.plain + " ", self.style)
|
||||||
rule_text.append(title_text)
|
rule_text.append(title_text)
|
||||||
rule_text.append(" " + right.plain, self.style)
|
rule_text.append(" " + right.plain, self.style)
|
||||||
|
elif self.align == "left":
|
||||||
|
title_text.truncate(width - 2, overflow="ellipsis")
|
||||||
|
rule_text.append(title_text)
|
||||||
|
rule_text.append(" ")
|
||||||
|
rule_text.append(characters * (width - rule_text.cell_len), self.style)
|
||||||
|
elif self.align == "right":
|
||||||
|
title_text.truncate(width - 2, overflow="ellipsis")
|
||||||
|
rule_text.append(characters * (width - title_text.cell_len - 1), self.style)
|
||||||
|
rule_text.append(" ")
|
||||||
|
rule_text.append(title_text)
|
||||||
|
|
||||||
rule_text.plain = set_cell_size(rule_text.plain, width)
|
rule_text.plain = set_cell_size(rule_text.plain, width)
|
||||||
yield rule_text
|
yield rule_text
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@ def render_scope(
|
||||||
*,
|
*,
|
||||||
title: TextType = None,
|
title: TextType = None,
|
||||||
sort_keys: bool = True,
|
sort_keys: bool = True,
|
||||||
indent_guides: bool = False
|
indent_guides: bool = False,
|
||||||
|
max_length: int = None,
|
||||||
|
max_string: int = None,
|
||||||
) -> "ConsoleRenderable":
|
) -> "ConsoleRenderable":
|
||||||
"""Render python variables in a given scope.
|
"""Render python variables in a given scope.
|
||||||
|
|
||||||
|
@ -25,6 +27,9 @@ def render_scope(
|
||||||
title (str, optional): Optional title. Defaults to None.
|
title (str, optional): Optional title. Defaults to None.
|
||||||
sort_keys (bool, optional): Enable sorting of items. Defaults to True.
|
sort_keys (bool, optional): Enable sorting of items. Defaults to True.
|
||||||
indent_guides (bool, optional): Enable indentaton guides. Defaults to False.
|
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:
|
Returns:
|
||||||
RenderableType: A renderable object.
|
RenderableType: A renderable object.
|
||||||
|
@ -46,7 +51,13 @@ def render_scope(
|
||||||
)
|
)
|
||||||
items_table.add_row(
|
items_table.add_row(
|
||||||
key_text,
|
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(
|
return Panel.fit(
|
||||||
items_table,
|
items_table,
|
||||||
|
|
88
rich/spinner.py
Normal file
88
rich/spinner.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
from typing import cast, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from ._spinners import SPINNERS
|
||||||
|
from .console import Console
|
||||||
|
from .measure import Measurement
|
||||||
|
from .style import StyleType
|
||||||
|
from .text import Text, TextType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .console import Console, ConsoleOptions, RenderResult
|
||||||
|
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
def __init__(
|
||||||
|
self, name: str, text: TextType = "", *, style: StyleType = None, speed=1.0
|
||||||
|
) -> None:
|
||||||
|
"""A spinner animation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of spinner (run python -m rich.spinner).
|
||||||
|
text (TextType, optional): Text to display at the right of the spinner. Defaults to "".
|
||||||
|
style (StyleType, optional): Style for sinner amimation. Defaults to None.
|
||||||
|
speed (float, optional): Speed factor for animation. Defaults to 1.0.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeyError: If name isn't one of the supported spinner animations.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
spinner = SPINNERS[name]
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(f"no spinner called {name!r}")
|
||||||
|
self.text = text
|
||||||
|
self.frames = cast(List[str], spinner["frames"])[:]
|
||||||
|
self.interval = cast(float, spinner["interval"])
|
||||||
|
self.start_time: Optional[float] = None
|
||||||
|
self.style = style
|
||||||
|
self.speed = speed
|
||||||
|
self.time = 0.0
|
||||||
|
|
||||||
|
def __rich_console__(
|
||||||
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
|
) -> "RenderResult":
|
||||||
|
time = console.get_time()
|
||||||
|
if self.start_time is None:
|
||||||
|
self.start_time = time
|
||||||
|
text = self.render(time - self.start_time)
|
||||||
|
yield text
|
||||||
|
|
||||||
|
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||||
|
text = self.render(0)
|
||||||
|
return Measurement.get(console, text, max_width)
|
||||||
|
|
||||||
|
def render(self, time: float) -> Text:
|
||||||
|
"""Render the spinner for a given time.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
time (float): Time in seconds.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text: A Text instance containing animation frame.
|
||||||
|
"""
|
||||||
|
frame_no = int((time * self.speed) / (self.interval / 1000.0))
|
||||||
|
frame = Text(self.frames[frame_no % len(self.frames)], style=self.style or "")
|
||||||
|
return Text.assemble(frame, " ", self.text) if self.text else frame
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from .columns import Columns
|
||||||
|
from .panel import Panel
|
||||||
|
from .live import Live
|
||||||
|
|
||||||
|
all_spinners = Columns(
|
||||||
|
[
|
||||||
|
Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
|
||||||
|
for spinner_name in sorted(SPINNERS.keys())
|
||||||
|
],
|
||||||
|
column_first=True,
|
||||||
|
expand=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with Live(
|
||||||
|
Panel(all_spinners, title="Spinners", border_style="blue"),
|
||||||
|
refresh_per_second=20,
|
||||||
|
) as live:
|
||||||
|
while True:
|
||||||
|
sleep(0.1)
|
127
rich/status.py
Normal file
127
rich/status.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .console import Console, RenderableType
|
||||||
|
from .live import Live
|
||||||
|
from .spinner import Spinner
|
||||||
|
from .style import StyleType
|
||||||
|
from .table import Table
|
||||||
|
|
||||||
|
|
||||||
|
class Status:
|
||||||
|
"""Displays a status indicator with a 'spinner' animation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (RenderableType): A status renderable (str or Text typically).
|
||||||
|
console (Console, optional): Console instance to use, or None for global console. Defaults to None.
|
||||||
|
spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots".
|
||||||
|
spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner".
|
||||||
|
speed (float, optional): Speed factor for spinner animation. Defaults to 1.0.
|
||||||
|
refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
status: RenderableType,
|
||||||
|
*,
|
||||||
|
console: Console = None,
|
||||||
|
spinner: str = "dots",
|
||||||
|
spinner_style: StyleType = "status.spinner",
|
||||||
|
speed: float = 1.0,
|
||||||
|
refresh_per_second: float = 12.5,
|
||||||
|
):
|
||||||
|
self.status = status
|
||||||
|
self.spinner = spinner
|
||||||
|
self.spinner_style = spinner_style
|
||||||
|
self.speed = speed
|
||||||
|
self._spinner = Spinner(spinner, style=spinner_style, speed=speed)
|
||||||
|
self._live = Live(
|
||||||
|
self.renderable,
|
||||||
|
console=console,
|
||||||
|
refresh_per_second=refresh_per_second,
|
||||||
|
transient=True,
|
||||||
|
)
|
||||||
|
self.update(
|
||||||
|
status=status, spinner=spinner, spinner_style=spinner_style, speed=speed
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def renderable(self) -> Table:
|
||||||
|
"""Get the renderable for the status (a table with spinner and status)."""
|
||||||
|
table = Table.grid(padding=1)
|
||||||
|
table.add_row(self._spinner, self.status)
|
||||||
|
return table
|
||||||
|
|
||||||
|
@property
|
||||||
|
def console(self) -> "Console":
|
||||||
|
"""Get the Console used by the Status objects."""
|
||||||
|
return self._live.console
|
||||||
|
|
||||||
|
def update(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
status: Optional[RenderableType] = None,
|
||||||
|
spinner: Optional[str] = None,
|
||||||
|
spinner_style: Optional[StyleType] = None,
|
||||||
|
speed: Optional[float] = None,
|
||||||
|
):
|
||||||
|
"""Update status.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (Optional[RenderableType], optional): New status renderable or None for no change. Defaults to None.
|
||||||
|
spinner (Optional[str], optional): New spinner or None for no change. Defaults to None.
|
||||||
|
spinner_style (Optional[StyleType], optional): New spinner style or None for no change. Defaults to None.
|
||||||
|
speed (Optional[float], optional): Speed factor for spinner animation or None for no change. Defaults to None.
|
||||||
|
"""
|
||||||
|
if status is not None:
|
||||||
|
self.status = status
|
||||||
|
if spinner is not None:
|
||||||
|
self.spinner = spinner
|
||||||
|
if spinner_style is not None:
|
||||||
|
self.spinner_style = spinner_style
|
||||||
|
if speed is not None:
|
||||||
|
self.speed = speed
|
||||||
|
self._spinner = Spinner(
|
||||||
|
self.spinner, style=self.spinner_style, speed=self.speed
|
||||||
|
)
|
||||||
|
self._live.update(self.renderable, refresh=True)
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
"""Start the status animation."""
|
||||||
|
self._live.start()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Stop the spinner animation."""
|
||||||
|
self._live.stop()
|
||||||
|
|
||||||
|
def __enter__(self) -> "Status":
|
||||||
|
self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from .console import Console
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
with console.status("[magenta]Covid detector booting up") as status:
|
||||||
|
sleep(3)
|
||||||
|
console.log("Importing advanced AI")
|
||||||
|
sleep(3)
|
||||||
|
console.log("Advanced Covid AI Ready")
|
||||||
|
sleep(3)
|
||||||
|
status.update(status="[bold blue] Scanning for Covid", spinner="earth")
|
||||||
|
sleep(3)
|
||||||
|
console.log("Found 10,000,000,000 copies of Covid32.exe")
|
||||||
|
sleep(3)
|
||||||
|
status.update(
|
||||||
|
status="[bold red]Moving Covid32.exe to Trash",
|
||||||
|
spinner="bouncingBall",
|
||||||
|
spinner_style="yellow",
|
||||||
|
)
|
||||||
|
sleep(5)
|
||||||
|
console.print("[bold green]Covid deleted successfully")
|
|
@ -11,7 +11,6 @@ from .padding import Padding, PaddingDimensions
|
||||||
from .protocol import is_renderable
|
from .protocol import is_renderable
|
||||||
from .segment import Segment
|
from .segment import Segment
|
||||||
from .style import Style, StyleType
|
from .style import Style, StyleType
|
||||||
from .styled import Styled
|
|
||||||
from .text import Text, TextType
|
from .text import Text, TextType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -81,6 +80,17 @@ class Column:
|
||||||
return self.ratio is not None
|
return self.ratio is not None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Row:
|
||||||
|
"""Information regarding a row."""
|
||||||
|
|
||||||
|
style: Optional[StyleType] = None
|
||||||
|
"""Style to apply to row."""
|
||||||
|
|
||||||
|
end_section: bool = False
|
||||||
|
"""Indicated end of section, which will force a line beneath the row."""
|
||||||
|
|
||||||
|
|
||||||
class _Cell(NamedTuple):
|
class _Cell(NamedTuple):
|
||||||
"""A single cell in a table."""
|
"""A single cell in a table."""
|
||||||
|
|
||||||
|
@ -122,6 +132,7 @@ class Table(JupyterMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
columns: List[Column]
|
columns: List[Column]
|
||||||
|
rows: List[Row]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -153,6 +164,7 @@ class Table(JupyterMixin):
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.columns: List[Column] = []
|
self.columns: List[Column] = []
|
||||||
|
self.rows: List[Row] = []
|
||||||
append_column = self.columns.append
|
append_column = self.columns.append
|
||||||
for index, header in enumerate(headers):
|
for index, header in enumerate(headers):
|
||||||
if isinstance(header, str):
|
if isinstance(header, str):
|
||||||
|
@ -184,7 +196,6 @@ class Table(JupyterMixin):
|
||||||
self.caption_style = caption_style
|
self.caption_style = caption_style
|
||||||
self.title_justify = title_justify
|
self.title_justify = title_justify
|
||||||
self.caption_justify = caption_justify
|
self.caption_justify = caption_justify
|
||||||
self._row_count = 0
|
|
||||||
self.row_styles = list(row_styles or [])
|
self.row_styles = list(row_styles or [])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -240,13 +251,17 @@ class Table(JupyterMixin):
|
||||||
@property
|
@property
|
||||||
def row_count(self) -> int:
|
def row_count(self) -> int:
|
||||||
"""Get the current number of rows."""
|
"""Get the current number of rows."""
|
||||||
return self._row_count
|
return len(self.rows)
|
||||||
|
|
||||||
def get_row_style(self, index: int) -> StyleType:
|
def get_row_style(self, console: "Console", index: int) -> StyleType:
|
||||||
"""Get the current row style."""
|
"""Get the current row style."""
|
||||||
|
style = Style.null()
|
||||||
if self.row_styles:
|
if self.row_styles:
|
||||||
return self.row_styles[index % len(self.row_styles)]
|
style += console.get_style(self.row_styles[index % len(self.row_styles)])
|
||||||
return Style.null()
|
row_style = self.rows[index].style
|
||||||
|
if row_style is not None:
|
||||||
|
style += console.get_style(row_style)
|
||||||
|
return style
|
||||||
|
|
||||||
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
def __rich_measure__(self, console: "Console", max_width: int) -> Measurement:
|
||||||
if self.width is not None:
|
if self.width is not None:
|
||||||
|
@ -255,9 +270,7 @@ class Table(JupyterMixin):
|
||||||
return Measurement(0, 0)
|
return Measurement(0, 0)
|
||||||
|
|
||||||
extra_width = self._extra_width
|
extra_width = self._extra_width
|
||||||
|
|
||||||
max_width = sum(self._calculate_column_widths(console, max_width - extra_width))
|
max_width = sum(self._calculate_column_widths(console, max_width - extra_width))
|
||||||
|
|
||||||
_measure_column = self._measure_column
|
_measure_column = self._measure_column
|
||||||
|
|
||||||
measurements = [
|
measurements = [
|
||||||
|
@ -342,7 +355,10 @@ class Table(JupyterMixin):
|
||||||
self.columns.append(column)
|
self.columns.append(column)
|
||||||
|
|
||||||
def add_row(
|
def add_row(
|
||||||
self, *renderables: Optional["RenderableType"], style: StyleType = None
|
self,
|
||||||
|
*renderables: Optional["RenderableType"],
|
||||||
|
style: StyleType = None,
|
||||||
|
end_section: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a row of renderables.
|
"""Add a row of renderables.
|
||||||
|
|
||||||
|
@ -350,15 +366,14 @@ class Table(JupyterMixin):
|
||||||
*renderables (None or renderable): Each cell in a row must be a renderable object (including str),
|
*renderables (None or renderable): Each cell in a row must be a renderable object (including str),
|
||||||
or ``None`` for a blank cell.
|
or ``None`` for a blank cell.
|
||||||
style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
|
style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
|
||||||
|
end_section (bool, optional): End a section and draw a line. Defaults to False.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
errors.NotRenderableError: If you add something that can't be rendered.
|
errors.NotRenderableError: If you add something that can't be rendered.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def add_cell(column: Column, renderable: "RenderableType") -> None:
|
def add_cell(column: Column, renderable: "RenderableType") -> None:
|
||||||
column._cells.append(
|
column._cells.append(renderable)
|
||||||
renderable if style is None else Styled(renderable, style)
|
|
||||||
)
|
|
||||||
|
|
||||||
cell_renderables: List[Optional["RenderableType"]] = list(renderables)
|
cell_renderables: List[Optional["RenderableType"]] = list(renderables)
|
||||||
|
|
||||||
|
@ -371,7 +386,7 @@ class Table(JupyterMixin):
|
||||||
for index, renderable in enumerate(cell_renderables):
|
for index, renderable in enumerate(cell_renderables):
|
||||||
if index == len(columns):
|
if index == len(columns):
|
||||||
column = Column(_index=index)
|
column = Column(_index=index)
|
||||||
for _ in range(self._row_count):
|
for _ in self.rows:
|
||||||
add_cell(column, Text(""))
|
add_cell(column, Text(""))
|
||||||
self.columns.append(column)
|
self.columns.append(column)
|
||||||
else:
|
else:
|
||||||
|
@ -384,7 +399,7 @@ class Table(JupyterMixin):
|
||||||
raise errors.NotRenderableError(
|
raise errors.NotRenderableError(
|
||||||
f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
|
f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
|
||||||
)
|
)
|
||||||
self._row_count += 1
|
self.rows.append(Row(style=style, end_section=end_section))
|
||||||
|
|
||||||
def __rich_console__(
|
def __rich_console__(
|
||||||
self, console: "Console", options: "ConsoleOptions"
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
|
@ -623,14 +638,11 @@ class Table(JupyterMixin):
|
||||||
table_style = console.get_style(self.style or "")
|
table_style = console.get_style(self.style or "")
|
||||||
|
|
||||||
border_style = table_style + console.get_style(self.border_style or "")
|
border_style = table_style + console.get_style(self.border_style or "")
|
||||||
rows: List[Tuple[_Cell, ...]] = list(
|
_column_cells = (
|
||||||
zip(
|
|
||||||
*(
|
|
||||||
self._get_cells(column_index, column)
|
self._get_cells(column_index, column)
|
||||||
for column_index, column in enumerate(self.columns)
|
for column_index, column in enumerate(self.columns)
|
||||||
)
|
)
|
||||||
)
|
row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))
|
||||||
)
|
|
||||||
_box = (
|
_box = (
|
||||||
self.box.substitute(
|
self.box.substitute(
|
||||||
options, safe=pick_bool(self.safe_box, console.safe_box)
|
options, safe=pick_bool(self.safe_box, console.safe_box)
|
||||||
|
@ -677,18 +689,23 @@ class Table(JupyterMixin):
|
||||||
get_row_style = self.get_row_style
|
get_row_style = self.get_row_style
|
||||||
get_style = console.get_style
|
get_style = console.get_style
|
||||||
|
|
||||||
for index, (first, last, row) in enumerate(loop_first_last(rows)):
|
for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):
|
||||||
header_row = first and show_header
|
header_row = first and show_header
|
||||||
footer_row = last and show_footer
|
footer_row = last and show_footer
|
||||||
|
row = (
|
||||||
|
self.rows[index - show_header]
|
||||||
|
if (not header_row and not footer_row)
|
||||||
|
else None
|
||||||
|
)
|
||||||
max_height = 1
|
max_height = 1
|
||||||
cells: List[List[List[Segment]]] = []
|
cells: List[List[List[Segment]]] = []
|
||||||
if header_row or footer_row:
|
if header_row or footer_row:
|
||||||
row_style = Style.null()
|
row_style = Style.null()
|
||||||
else:
|
else:
|
||||||
row_style = get_style(
|
row_style = get_style(
|
||||||
get_row_style(index - 1 if show_header else index)
|
get_row_style(console, index - 1 if show_header else index)
|
||||||
)
|
)
|
||||||
for width, cell, column in zip(widths, row, columns):
|
for width, cell, column in zip(widths, row_cell, columns):
|
||||||
render_options = options.update(
|
render_options = options.update(
|
||||||
width=width,
|
width=width,
|
||||||
justify=column.justify,
|
justify=column.justify,
|
||||||
|
@ -703,7 +720,9 @@ class Table(JupyterMixin):
|
||||||
cells.append(lines)
|
cells.append(lines)
|
||||||
|
|
||||||
cells[:] = [
|
cells[:] = [
|
||||||
_Segment.set_shape(_cell, width, max_height, style=table_style)
|
_Segment.set_shape(
|
||||||
|
_cell, width, max_height, style=table_style + row_style
|
||||||
|
)
|
||||||
for width, _cell in zip(widths, cells)
|
for width, _cell in zip(widths, cells)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -743,16 +762,16 @@ class Table(JupyterMixin):
|
||||||
_box.get_row(widths, "head", edge=show_edge), border_style
|
_box.get_row(widths, "head", edge=show_edge), border_style
|
||||||
)
|
)
|
||||||
yield new_line
|
yield new_line
|
||||||
if _box and (show_lines or leading):
|
end_section = row and row.end_section
|
||||||
|
if _box and (show_lines or leading or end_section):
|
||||||
if (
|
if (
|
||||||
not last
|
not last
|
||||||
and not (show_footer and index >= len(rows) - 2)
|
and not (show_footer and index >= len(row_cells) - 2)
|
||||||
and not (show_header and header_row)
|
and not (show_header and header_row)
|
||||||
):
|
):
|
||||||
if leading:
|
if leading:
|
||||||
for _ in range(leading):
|
|
||||||
yield _Segment(
|
yield _Segment(
|
||||||
_box.get_row(widths, "mid", edge=show_edge),
|
_box.get_row(widths, "mid", edge=show_edge) * leading,
|
||||||
border_style,
|
border_style,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -781,10 +800,24 @@ if __name__ == "__main__": # pragma: no cover
|
||||||
table.add_column("Title", style="magenta")
|
table.add_column("Title", style="magenta")
|
||||||
table.add_column("Box Office", justify="right", style="green")
|
table.add_column("Box Office", justify="right", style="green")
|
||||||
|
|
||||||
table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
|
table.add_row(
|
||||||
|
"Dec 20, 2019",
|
||||||
|
"Star Wars: The Rise of Skywalker",
|
||||||
|
"$952,110,690",
|
||||||
|
)
|
||||||
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
|
table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
|
||||||
table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
|
table.add_row(
|
||||||
table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
|
"Dec 15, 2017",
|
||||||
|
"Star Wars Ep. V111: The Last Jedi",
|
||||||
|
"$1,332,539,889",
|
||||||
|
style="on black",
|
||||||
|
end_section=True,
|
||||||
|
)
|
||||||
|
table.add_row(
|
||||||
|
"Dec 16, 2016",
|
||||||
|
"Rogue One: A Star Wars Story",
|
||||||
|
"$1,332,439,889",
|
||||||
|
)
|
||||||
|
|
||||||
def header(text: str) -> None:
|
def header(text: str) -> None:
|
||||||
console.print()
|
console.print()
|
||||||
|
|
39
rich/text.py
39
rich/text.py
|
@ -1,4 +1,5 @@
|
||||||
from functools import partial, reduce
|
from functools import partial, reduce
|
||||||
|
from io import UnsupportedOperation
|
||||||
from math import gcd
|
from math import gcd
|
||||||
import re
|
import re
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
@ -19,7 +20,7 @@ from typing import (
|
||||||
from ._loop import loop_last
|
from ._loop import loop_last
|
||||||
from ._pick import pick_bool
|
from ._pick import pick_bool
|
||||||
from ._wrap import divide_line
|
from ._wrap import divide_line
|
||||||
from .align import AlignValues
|
from .align import AlignMethod
|
||||||
from .cells import cell_len, set_cell_size
|
from .cells import cell_len, set_cell_size
|
||||||
from .containers import Lines
|
from .containers import Lines
|
||||||
from .control import strip_control_codes
|
from .control import strip_control_codes
|
||||||
|
@ -182,6 +183,32 @@ class Text(JupyterMixin):
|
||||||
return other.plain in self.plain
|
return other.plain in self.plain
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def __getitem__(self, slice: Union[int, slice]) -> "Text":
|
||||||
|
def get_text_at(offset) -> "Text":
|
||||||
|
_Span = Span
|
||||||
|
text = Text(
|
||||||
|
self.plain[offset],
|
||||||
|
spans=[
|
||||||
|
_Span(0, 1, style)
|
||||||
|
for start, end, style in self._spans
|
||||||
|
if end > offset >= start
|
||||||
|
],
|
||||||
|
end="",
|
||||||
|
)
|
||||||
|
return text
|
||||||
|
|
||||||
|
if isinstance(slice, int):
|
||||||
|
return get_text_at(slice)
|
||||||
|
else:
|
||||||
|
start, stop, step = slice.indices(len(self.plain))
|
||||||
|
if step == 1:
|
||||||
|
lines = self.divide([start, stop])
|
||||||
|
return lines[1]
|
||||||
|
else:
|
||||||
|
# This would be a bit of work to implement efficiently
|
||||||
|
# For now, its not required
|
||||||
|
raise TypeError("slices with step!=1 are not supported")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cell_len(self) -> int:
|
def cell_len(self) -> int:
|
||||||
"""Get the number of cells required to render this text."""
|
"""Get the number of cells required to render this text."""
|
||||||
|
@ -521,10 +548,10 @@ class Text(JupyterMixin):
|
||||||
Iterable[Segment]: Result of render that may be written to the console.
|
Iterable[Segment]: Result of render that may be written to the console.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_Segment = Segment
|
||||||
text = self.plain
|
text = self.plain
|
||||||
null_style = Style.null()
|
|
||||||
enumerated_spans = list(enumerate(self._spans, 1))
|
enumerated_spans = list(enumerate(self._spans, 1))
|
||||||
get_style = partial(console.get_style, default=null_style)
|
get_style = partial(console.get_style, default=Style.null())
|
||||||
style_map = {index: get_style(span.style) for index, span in enumerated_spans}
|
style_map = {index: get_style(span.style) for index, span in enumerated_spans}
|
||||||
style_map[0] = get_style(self.style)
|
style_map[0] = get_style(self.style)
|
||||||
|
|
||||||
|
@ -540,7 +567,6 @@ class Text(JupyterMixin):
|
||||||
stack_append = stack.append
|
stack_append = stack.append
|
||||||
stack_pop = stack.remove
|
stack_pop = stack.remove
|
||||||
|
|
||||||
_Segment = Segment
|
|
||||||
style_cache: Dict[Tuple[Style, ...], Style] = {}
|
style_cache: Dict[Tuple[Style, ...], Style] = {}
|
||||||
style_cache_get = style_cache.get
|
style_cache_get = style_cache.get
|
||||||
combine = Style.combine
|
combine = Style.combine
|
||||||
|
@ -725,11 +751,11 @@ class Text(JupyterMixin):
|
||||||
if count:
|
if count:
|
||||||
self.plain = f"{self.plain}{character * count}"
|
self.plain = f"{self.plain}{character * count}"
|
||||||
|
|
||||||
def align(self, align: AlignValues, width: int, character: str = " ") -> None:
|
def align(self, align: AlignMethod, width: int, character: str = " ") -> None:
|
||||||
"""Align text to a given width.
|
"""Align text to a given width.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
align (AlignValues): One of "left", "center", or "right".
|
align (AlignMethod): One of "left", "center", or "right".
|
||||||
width (int): Desired width.
|
width (int): Desired width.
|
||||||
character (str, optional): Character to pad with. Defaults to " ".
|
character (str, optional): Character to pad with. Defaults to " ".
|
||||||
"""
|
"""
|
||||||
|
@ -930,6 +956,7 @@ class Text(JupyterMixin):
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
span, new_span = span.split(line_end)
|
span, new_span = span.split(line_end)
|
||||||
|
if span:
|
||||||
new_lines[line_index]._spans.append(span.move(-line_start))
|
new_lines[line_index]._spans.append(span.move(-line_start))
|
||||||
if new_span is None:
|
if new_span is None:
|
||||||
break
|
break
|
||||||
|
|
|
@ -42,6 +42,9 @@ from .theme import Theme
|
||||||
|
|
||||||
WINDOWS = platform.system() == "Windows"
|
WINDOWS = platform.system() == "Windows"
|
||||||
|
|
||||||
|
LOCALS_MAX_LENGTH = 10
|
||||||
|
LOCALS_MAX_STRING = 80
|
||||||
|
|
||||||
|
|
||||||
def install(
|
def install(
|
||||||
*,
|
*,
|
||||||
|
@ -146,6 +149,9 @@ class Traceback:
|
||||||
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
|
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.
|
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.
|
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__(
|
def __init__(
|
||||||
|
@ -157,6 +163,8 @@ class Traceback:
|
||||||
word_wrap: bool = False,
|
word_wrap: bool = False,
|
||||||
show_locals: bool = False,
|
show_locals: bool = False,
|
||||||
indent_guides: bool = True,
|
indent_guides: bool = True,
|
||||||
|
locals_max_length: int = LOCALS_MAX_LENGTH,
|
||||||
|
locals_max_string: int = LOCALS_MAX_STRING,
|
||||||
):
|
):
|
||||||
if trace is None:
|
if trace is None:
|
||||||
exc_type, exc_value, traceback = sys.exc_info()
|
exc_type, exc_value, traceback = sys.exc_info()
|
||||||
|
@ -174,6 +182,8 @@ class Traceback:
|
||||||
self.word_wrap = word_wrap
|
self.word_wrap = word_wrap
|
||||||
self.show_locals = show_locals
|
self.show_locals = show_locals
|
||||||
self.indent_guides = indent_guides
|
self.indent_guides = indent_guides
|
||||||
|
self.locals_max_length = locals_max_length
|
||||||
|
self.locals_max_string = locals_max_string
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_exception(
|
def from_exception(
|
||||||
|
@ -187,6 +197,8 @@ class Traceback:
|
||||||
word_wrap: bool = False,
|
word_wrap: bool = False,
|
||||||
show_locals: bool = False,
|
show_locals: bool = False,
|
||||||
indent_guides: bool = True,
|
indent_guides: bool = True,
|
||||||
|
locals_max_length: int = LOCALS_MAX_LENGTH,
|
||||||
|
locals_max_string: int = LOCALS_MAX_STRING,
|
||||||
) -> "Traceback":
|
) -> "Traceback":
|
||||||
"""Create a traceback from exception info
|
"""Create a traceback from exception info
|
||||||
|
|
||||||
|
@ -200,6 +212,9 @@ class Traceback:
|
||||||
word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
|
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.
|
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.
|
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:
|
Returns:
|
||||||
Traceback: A Traceback instance that may be printed.
|
Traceback: A Traceback instance that may be printed.
|
||||||
|
@ -215,6 +230,8 @@ class Traceback:
|
||||||
word_wrap=word_wrap,
|
word_wrap=word_wrap,
|
||||||
show_locals=show_locals,
|
show_locals=show_locals,
|
||||||
indent_guides=indent_guides,
|
indent_guides=indent_guides,
|
||||||
|
locals_max_length=locals_max_length,
|
||||||
|
locals_max_string=locals_max_string,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -224,6 +241,8 @@ class Traceback:
|
||||||
exc_value: BaseException,
|
exc_value: BaseException,
|
||||||
traceback: Optional[TracebackType],
|
traceback: Optional[TracebackType],
|
||||||
show_locals: bool = False,
|
show_locals: bool = False,
|
||||||
|
locals_max_length: int = LOCALS_MAX_LENGTH,
|
||||||
|
locals_max_string: int = LOCALS_MAX_STRING,
|
||||||
) -> Trace:
|
) -> Trace:
|
||||||
"""Extract traceback information.
|
"""Extract traceback information.
|
||||||
|
|
||||||
|
@ -232,6 +251,9 @@ class Traceback:
|
||||||
exc_value (BaseException): Exception value.
|
exc_value (BaseException): Exception value.
|
||||||
traceback (TracebackType): Python Traceback object.
|
traceback (TracebackType): Python Traceback object.
|
||||||
show_locals (bool, optional): Enable display of local variables. Defaults to False.
|
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:
|
Returns:
|
||||||
Trace: A Trace instance which you can use to construct a `Traceback`.
|
Trace: A Trace instance which you can use to construct a `Traceback`.
|
||||||
|
@ -266,7 +288,11 @@ class Traceback:
|
||||||
lineno=line_no,
|
lineno=line_no,
|
||||||
name=frame_summary.f_code.co_name,
|
name=frame_summary.f_code.co_name,
|
||||||
locals={
|
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()
|
for key, value in frame_summary.f_locals.items()
|
||||||
}
|
}
|
||||||
if show_locals
|
if show_locals
|
||||||
|
@ -460,6 +486,8 @@ class Traceback:
|
||||||
frame.locals,
|
frame.locals,
|
||||||
title="locals",
|
title="locals",
|
||||||
indent_guides=self.indent_guides,
|
indent_guides=self.indent_guides,
|
||||||
|
max_length=self.locals_max_length,
|
||||||
|
max_string=self.locals_max_string,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
padding=1,
|
padding=1,
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ from rich.console import CaptureError, Console, ConsoleOptions, render_group
|
||||||
from rich.measure import measure_renderables
|
from rich.measure import measure_renderables
|
||||||
from rich.pager import SystemPager
|
from rich.pager import SystemPager
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
from rich.status import Status
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,6 +98,21 @@ def test_print():
|
||||||
assert console.file.getvalue() == "foo\n"
|
assert console.file.getvalue() == "foo\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_log():
|
||||||
|
console = Console(
|
||||||
|
file=io.StringIO(),
|
||||||
|
width=80,
|
||||||
|
color_system="truecolor",
|
||||||
|
log_time_format="TIME",
|
||||||
|
log_path=False,
|
||||||
|
)
|
||||||
|
console.log("foo", style="red")
|
||||||
|
expected = "\x1b[2;36mTIME\x1b[0m\x1b[2;36m \x1b[0m\x1b[31mfoo\x1b[0m\x1b[31m \x1b[0m\n"
|
||||||
|
result = console.file.getvalue()
|
||||||
|
print(repr(result))
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
def test_print_empty():
|
def test_print_empty():
|
||||||
console = Console(file=io.StringIO(), color_system="truecolor")
|
console = Console(file=io.StringIO(), color_system="truecolor")
|
||||||
console.print()
|
console.print()
|
||||||
|
@ -218,6 +234,12 @@ def test_input_password(monkeypatch, capsys):
|
||||||
assert user_input == "bar"
|
assert user_input == "bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_status():
|
||||||
|
console = Console(file=io.StringIO(), force_terminal=True, width=20)
|
||||||
|
status = console.status("foo")
|
||||||
|
assert isinstance(status, Status)
|
||||||
|
|
||||||
|
|
||||||
def test_justify_none():
|
def test_justify_none():
|
||||||
console = Console(file=io.StringIO(), force_terminal=True, width=20)
|
console = Console(file=io.StringIO(), force_terminal=True, width=20)
|
||||||
console.print("FOO", justify=None)
|
console.print("FOO", justify=None)
|
||||||
|
|
160
tests/test_live.py
Normal file
160
tests/test_live.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
# encoding=utf-8
|
||||||
|
import io
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# import pytest
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.live import Live
|
||||||
|
|
||||||
|
|
||||||
|
def create_capture_console(
|
||||||
|
*, width: int = 60, height: int = 80, force_terminal: Optional[bool] = True
|
||||||
|
) -> Console:
|
||||||
|
return Console(
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
file=io.StringIO(),
|
||||||
|
force_terminal=force_terminal,
|
||||||
|
legacy_windows=False,
|
||||||
|
color_system=None, # use no color system to reduce complexity of output
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_live_state() -> None:
|
||||||
|
|
||||||
|
with Live("") as live:
|
||||||
|
assert live._started
|
||||||
|
live.start()
|
||||||
|
|
||||||
|
assert live.renderable == ""
|
||||||
|
|
||||||
|
assert live._started
|
||||||
|
live.stop()
|
||||||
|
assert not live._started
|
||||||
|
|
||||||
|
assert not live._started
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display() -> None:
|
||||||
|
console = create_capture_console()
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=False) as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_transient() -> None:
|
||||||
|
console = create_capture_console()
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=False, transient=True) as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h\r\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_overflow_ellipsis() -> None:
|
||||||
|
console = create_capture_console(height=5)
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(
|
||||||
|
console=console, auto_refresh=False, vertical_overflow="ellipsis"
|
||||||
|
) as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n ... \r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_overflow_crop() -> None:
|
||||||
|
console = create_capture_console(height=5)
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=False, vertical_overflow="crop") as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_overflow_visible() -> None:
|
||||||
|
console = create_capture_console(height=5)
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=False, vertical_overflow="visible") as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "\x1b[?25lStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_autorefresh() -> None:
|
||||||
|
"""Test generating a table but using auto-refresh from threading"""
|
||||||
|
console = create_capture_console()
|
||||||
|
|
||||||
|
console = create_capture_console(height=5)
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=True, vertical_overflow="visible") as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display)
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
# no way to truly test w/ multithreading, just make sure it doesn't crash
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_console_redirect() -> None:
|
||||||
|
console = create_capture_console()
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=False) as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
console.print(f"Running step {step}")
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "\x1b[?25lRunning step 0\n\r\x1b[2KStep 0\n\r\x1b[2K\x1b[1A\x1b[2KRunning step 1\nStep 0\n\r\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 2\nStep 0\nStep 1\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 3\nStep 0\nStep 1\nStep 2\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 4\nStep 0\nStep 1\nStep 2\nStep 3\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 5\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 6\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 7\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 8\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KRunning step 9\nStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\r\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2K\x1b[1A\x1b[2KStep 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n\n\x1b[?25h"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_growing_display_file_console() -> None:
|
||||||
|
console = create_capture_console(force_terminal=False)
|
||||||
|
console.begin_capture()
|
||||||
|
with Live(console=console, auto_refresh=False) as live:
|
||||||
|
display = ""
|
||||||
|
for step in range(10):
|
||||||
|
display += f"Step {step}\n"
|
||||||
|
live.update(display, refresh=True)
|
||||||
|
output = console.end_capture()
|
||||||
|
assert (
|
||||||
|
output
|
||||||
|
== "Step 0\nStep 1\nStep 2\nStep 3\nStep 4\nStep 5\nStep 6\nStep 7\nStep 8\nStep 9\n"
|
||||||
|
)
|
|
@ -45,6 +45,14 @@ def test_log():
|
||||||
assert rendered == expected
|
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__":
|
if __name__ == "__main__":
|
||||||
render = render_log()
|
render = render_log()
|
||||||
print(render)
|
print(render)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# encoding=utf-8
|
# encoding=utf-8
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from time import time
|
from time import sleep
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -143,8 +143,10 @@ def make_progress() -> Progress:
|
||||||
|
|
||||||
def render_progress() -> str:
|
def render_progress() -> str:
|
||||||
progress = make_progress()
|
progress = make_progress()
|
||||||
|
progress.start() # superfluous noop
|
||||||
with progress:
|
with progress:
|
||||||
pass
|
pass
|
||||||
|
progress.stop() # superfluous noop
|
||||||
progress_render = progress.console.file.getvalue()
|
progress_render = progress.console.file.getvalue()
|
||||||
return progress_render
|
return progress_render
|
||||||
|
|
||||||
|
@ -328,9 +330,20 @@ def test_progress_create() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_thread() -> None:
|
def test_refresh_thread() -> None:
|
||||||
progress = Progress()
|
class MockProgress:
|
||||||
thread = _RefreshThread(progress, 10)
|
def __init__(self):
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.count += 1
|
||||||
|
|
||||||
|
progress = MockProgress()
|
||||||
|
thread = _RefreshThread(progress, 100)
|
||||||
assert thread.progress == progress
|
assert thread.progress == progress
|
||||||
|
thread.start()
|
||||||
|
sleep(0.2)
|
||||||
|
thread.stop()
|
||||||
|
assert progress.count >= 1
|
||||||
|
|
||||||
|
|
||||||
def test_track_thread() -> None:
|
def test_track_thread() -> None:
|
||||||
|
@ -372,6 +385,42 @@ def test_reset() -> None:
|
||||||
assert not task._progress
|
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__":
|
if __name__ == "__main__":
|
||||||
_render = render_progress()
|
_render = render_progress()
|
||||||
print(_render)
|
print(_render)
|
||||||
|
|
|
@ -24,6 +24,25 @@ def test_rule():
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_rule_error():
|
||||||
|
console = Console(width=16, file=io.StringIO(), legacy_windows=False)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
console.rule("foo", align="foo")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rule_align():
|
||||||
|
console = Console(width=16, file=io.StringIO(), legacy_windows=False)
|
||||||
|
console.rule("foo")
|
||||||
|
console.rule("foo", align="left")
|
||||||
|
console.rule("foo", align="center")
|
||||||
|
console.rule("foo", align="right")
|
||||||
|
console.rule()
|
||||||
|
result = console.file.getvalue()
|
||||||
|
print(repr(result))
|
||||||
|
expected = "───── foo ──────\nfoo ────────────\n───── foo ──────\n──────────── foo\n────────────────\n"
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
def test_rule_cjk():
|
def test_rule_cjk():
|
||||||
console = Console(
|
console = Console(
|
||||||
width=16,
|
width=16,
|
||||||
|
|
42
tests/test_spinner.py
Normal file
42
tests/test_spinner.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
from time import time
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.measure import Measurement
|
||||||
|
from rich.spinner import Spinner
|
||||||
|
|
||||||
|
|
||||||
|
def test_spinner_create():
|
||||||
|
spinner = Spinner("dots")
|
||||||
|
assert spinner.time == 0.0
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
Spinner("foobar")
|
||||||
|
|
||||||
|
|
||||||
|
def test_spinner_render():
|
||||||
|
time = 0.0
|
||||||
|
|
||||||
|
def get_time():
|
||||||
|
nonlocal time
|
||||||
|
return time
|
||||||
|
|
||||||
|
console = Console(
|
||||||
|
width=80, color_system=None, force_terminal=True, get_time=get_time
|
||||||
|
)
|
||||||
|
console.begin_capture()
|
||||||
|
spinner = Spinner("dots", "Foo")
|
||||||
|
console.print(spinner)
|
||||||
|
time += 80 / 1000
|
||||||
|
console.print(spinner)
|
||||||
|
result = console.end_capture()
|
||||||
|
print(repr(result))
|
||||||
|
expected = "⠋ Foo\n⠙ Foo\n"
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_rich_measure():
|
||||||
|
console = Console(width=80, color_system=None, force_terminal=True)
|
||||||
|
spinner = Spinner("dots", "Foo")
|
||||||
|
min_width, max_width = Measurement.get(console, spinner, 80)
|
||||||
|
assert min_width == 3
|
||||||
|
assert max_width == 5
|
21
tests/test_status.py
Normal file
21
tests/test_status.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.status import Status
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
|
||||||
|
def test_status():
|
||||||
|
|
||||||
|
console = Console(
|
||||||
|
color_system=None, width=80, legacy_windows=False, get_time=lambda: 0.0
|
||||||
|
)
|
||||||
|
status = Status("foo", console=console)
|
||||||
|
assert status.console == console
|
||||||
|
status.update(status="bar", spinner="dots2", spinner_style="red", speed=2.0)
|
||||||
|
|
||||||
|
assert isinstance(status.renderable, Table)
|
||||||
|
|
||||||
|
# TODO: Testing output is tricky with threads
|
||||||
|
with status:
|
||||||
|
sleep(0.2)
|
|
@ -451,6 +451,14 @@ def test_render():
|
||||||
assert output == expected
|
assert output == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_simple():
|
||||||
|
console = Console(width=80)
|
||||||
|
console.begin_capture()
|
||||||
|
console.print(Text("foo"))
|
||||||
|
result = console.end_capture()
|
||||||
|
assert result == "foo\n"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"print_text,result",
|
"print_text,result",
|
||||||
[
|
[
|
||||||
|
@ -636,3 +644,18 @@ foo = [
|
||||||
print(repr(result.plain))
|
print(repr(result.plain))
|
||||||
expected = "for a in range(10):\n│ print(a)\n\nfoo = [\n│ 1,\n│ {\n│ │ 2\n│ }\n]\n"
|
expected = "for a in range(10):\n│ print(a)\n\nfoo = [\n│ 1,\n│ {\n│ │ 2\n│ }\n]\n"
|
||||||
assert result.plain == expected
|
assert result.plain == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_slice():
|
||||||
|
|
||||||
|
text = Text.from_markup("[red]foo [bold]bar[/red] baz[/bold]")
|
||||||
|
assert text[0] == Text("f", spans=[Span(0, 1, "red")])
|
||||||
|
assert text[4] == Text("b", spans=[Span(0, 1, "red"), Span(0, 1, "bold")])
|
||||||
|
|
||||||
|
assert text[:3] == Text("foo", spans=[Span(0, 3, "red")])
|
||||||
|
assert text[:4] == Text("foo ", spans=[Span(0, 4, "red")])
|
||||||
|
assert text[:5] == Text("foo b", spans=[Span(0, 5, "red"), Span(4, 5, "bold")])
|
||||||
|
assert text[4:] == Text("bar baz", spans=[Span(0, 3, "red"), Span(0, 7, "bold")])
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
text[::-1]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue