Merge branch 'horizontal-layout' of github.com:willmcgugan/textual into horizontal-layout

This commit is contained in:
Darren Burns
2022-02-04 11:55:05 +00:00
20 changed files with 1258 additions and 307 deletions

View File

@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.1.15] - 2022-01-31
### Added
- Added Windows Driver
## [1.1.14] - 2022-01-09
### Changed
- Updated Rich dependency to 11.X
## [0.1.13] - 2022-01-01

View File

@@ -2,9 +2,11 @@
![screenshot](./imgs/textual.png)
Textual is a TUI (Text User Interface) framework for Python inspired by modern web development.
Textual is a TUI (Text User Interface) framework for Python inspired by modern web development. Currently a **Work in Progress**.
> ⚠ **NOTE:** We ([Textualize.io](https://www.textualize.io)) are hard at work on the **css** branch. We will be maintain the 0.1.0 branch for the near future but may not be able to accept API changes. If you would like to contribute code via a PR, please raise a discussion first, to avoid disapointment.
**NOTE:** This project is currently a work in progress, but usable by brave souls who don't mind some API instability between updates.
Follow [@willmcgugan](https://twitter.com/willmcgugan) for progress updates, or post in Discussions if you have any requests / suggestions.
@@ -12,7 +14,7 @@ Follow [@willmcgugan](https://twitter.com/willmcgugan) for progress updates, or
## Compatibility
Textual currently runs on **MacOS / Linux only**. Windows support is in the pipeline.
Textual currently runs on **MacOS / Linux / Window**.
## How it works

View File

@@ -32,5 +32,7 @@ class SmoothApp(App):
self.bar.layout_offset_x = -40
# self.set_timer(10, lambda: self.action("quit"))
SmoothApp.run(log="textual.log")
SmoothApp.run(log="textual.log", log_verbosity=2)

424
poetry.lock generated
View File

@@ -19,32 +19,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "21.2.0"
version = "21.4.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
[[package]]
name = "backports.entry-points-selectable"
version = "1.1.0"
description = "Compatibility shim providing selectable entry points for older implementations"
category = "dev"
optional = false
python-versions = ">=2.7"
[package.dependencies]
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "black"
@@ -91,7 +76,7 @@ python-versions = ">=3.6.1"
[[package]]
name = "click"
version = "8.0.1"
version = "8.0.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
@@ -122,18 +107,18 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "coverage"
version = "5.5"
version = "6.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"
python-versions = ">=3.7"
[package.extras]
toml = ["toml"]
toml = ["tomli"]
[[package]]
name = "distlib"
version = "0.3.2"
version = "0.3.4"
description = "Distribution utilities"
category = "dev"
optional = false
@@ -141,15 +126,19 @@ python-versions = "*"
[[package]]
name = "filelock"
version = "3.0.12"
version = "3.4.2"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = "*"
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
[[package]]
name = "ghp-import"
version = "2.0.1"
version = "2.0.2"
description = "Copy your docs directly to the gh-pages branch."
category = "dev"
optional = false
@@ -159,26 +148,26 @@ python-versions = "*"
python-dateutil = ">=2.8.1"
[package.extras]
dev = ["twine", "markdown", "flake8"]
dev = ["twine", "markdown", "flake8", "wheel"]
[[package]]
name = "identify"
version = "2.2.13"
version = "2.4.6"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.6.1"
python-versions = ">=3.7"
[package.extras]
license = ["editdistance-s"]
license = ["ukkonen"]
[[package]]
name = "importlib-metadata"
version = "4.6.4"
version = "4.10.1"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
@@ -187,7 +176,7 @@ zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]]
name = "iniconfig"
@@ -199,7 +188,7 @@ python-versions = "*"
[[package]]
name = "jinja2"
version = "3.0.1"
version = "3.0.3"
description = "A very fast and expressive template engine."
category = "dev"
optional = false
@@ -213,14 +202,14 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "markdown"
version = "3.3.4"
version = "3.3.6"
description = "Python implementation of Markdown."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
testing = ["coverage", "pyyaml"]
@@ -278,7 +267,7 @@ mkdocs = ">=1.1,<2.0"
[[package]]
name = "mkdocs-material"
version = "7.2.5"
version = "7.3.0"
description = "A Material Design theme for MkDocs"
category = "dev"
optional = false
@@ -293,14 +282,11 @@ pymdown-extensions = ">=7.0"
[[package]]
name = "mkdocs-material-extensions"
version = "1.0.1"
version = "1.0.3"
description = "Extension pack for Python Markdown."
category = "dev"
optional = false
python-versions = ">=3.5"
[package.dependencies]
mkdocs-material = ">=5.0.0"
python-versions = ">=3.6"
[[package]]
name = "mkdocstrings"
@@ -355,14 +341,14 @@ python-versions = "*"
[[package]]
name = "packaging"
version = "21.0"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2"
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pathspec"
@@ -374,11 +360,11 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "platformdirs"
version = "2.2.0"
version = "2.4.1"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.extras]
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
@@ -386,21 +372,22 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
[[package]]
name = "pluggy"
version = "0.13.1"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = ">=3.6"
[package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "2.14.0"
version = "2.17.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
@@ -417,15 +404,15 @@ virtualenv = ">=20.0.8"
[[package]]
name = "py"
version = "1.10.0"
version = "1.11.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.*"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pygments"
version = "2.10.0"
version = "2.11.2"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
@@ -444,15 +431,18 @@ Markdown = ">=3.2"
[[package]]
name = "pyparsing"
version = "2.4.7"
version = "3.0.7"
description = "Python parsing module"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
python-versions = ">=3.6"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pytest"
version = "6.2.4"
version = "6.2.5"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
@@ -465,7 +455,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<1.0.0a1"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
toml = "*"
@@ -517,11 +507,11 @@ numpy-style = ["docstring_parser (>=0.7.3,<0.8.0)"]
[[package]]
name = "pyyaml"
version = "5.4.1"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
python-versions = ">=3.6"
[[package]]
name = "pyyaml-env-tag"
@@ -536,7 +526,7 @@ pyyaml = "*"
[[package]]
name = "rich"
version = "10.16.1"
version = "11.1.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main"
optional = false
@@ -593,51 +583,50 @@ python-versions = "*"
[[package]]
name = "virtualenv"
version = "20.7.2"
version = "20.13.0"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[package.dependencies]
"backports.entry-points-selectable" = ">=1.0.4"
distlib = ">=0.3.1,<1"
filelock = ">=3.0.0,<4"
filelock = ">=3.2,<4"
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
platformdirs = ">=2,<3"
six = ">=1.9.0,<2"
[package.extras]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
[[package]]
name = "watchdog"
version = "2.1.5"
version = "2.1.6"
description = "Filesystem events monitoring"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
watchmedo = ["PyYAML (>=3.10)"]
[[package]]
name = "zipp"
version = "3.5.0"
version = "3.7.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.6"
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "d9fa84daf9d3ae9839ebb6e846b73749c4c7737bdcf1160257e3b6ecf7086160"
content-hash = "cdb4f091bb4090e971acb3f64c73ee65d099ea7cbf76455ac6e7a99cf6552190"
[metadata.files]
astunparse = [
@@ -649,12 +638,8 @@ atomicwrites = [
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
{file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
]
"backports.entry-points-selectable" = [
{file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"},
{file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
black = [
{file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"},
@@ -669,8 +654,8 @@ cfgv = [
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
click = [
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
@@ -681,89 +666,82 @@ commonmark = [
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
]
coverage = [
{file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
{file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
{file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
{file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
{file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
{file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
{file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
{file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
{file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
{file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
{file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
{file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
{file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
{file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
{file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
{file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
{file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
{file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
{file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
{file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
{file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
{file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
{file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
{file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
{file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
{file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
{file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
{file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
{file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
{file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
{file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
{file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
{file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
{file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
{file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
{file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
{file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
{file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
{file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
{file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
{file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
{file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
{file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
{file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
{file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
{file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
{file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
{file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
{file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
{file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
{file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
{file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
{file = "coverage-6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e8071e7d9ba9f457fc674afc3de054450be2c9b195c470147fbbc082468d8ff7"},
{file = "coverage-6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86c91c511853dfda81c2cf2360502cb72783f4b7cebabef27869f00cbe1db07d"},
{file = "coverage-6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4ce3b647bd1792d4394f5690d9df6dc035b00bcdbc5595099c01282a59ae01"},
{file = "coverage-6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a491e159294d756e7fc8462f98175e2d2225e4dbe062cca7d3e0d5a75ba6260"},
{file = "coverage-6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d008e0f67ac800b0ca04d7914b8501312c8c6c00ad8c7ba17754609fae1231a"},
{file = "coverage-6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4578728c36de2801c1deb1c6b760d31883e62e33f33c7ba8f982e609dc95167d"},
{file = "coverage-6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7ee317486593193e066fc5e98ac0ce712178c21529a85c07b7cb978171f25d53"},
{file = "coverage-6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2bc85664b06ba42d14bb74d6ddf19d8bfc520cb660561d2d9ce5786ae72f71b5"},
{file = "coverage-6.3-cp310-cp310-win32.whl", hash = "sha256:27a94db5dc098c25048b0aca155f5fac674f2cf1b1736c5272ba28ead2fc267e"},
{file = "coverage-6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bde4aeabc0d1b2e52c4036c54440b1ad05beeca8113f47aceb4998bb7471e2c2"},
{file = "coverage-6.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:509c68c3e2015022aeda03b003dd68fa19987cdcf64e9d4edc98db41cfc45d30"},
{file = "coverage-6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e4ff163602c5c77e7bb4ea81ba5d3b793b4419f8acd296aae149370902cf4e92"},
{file = "coverage-6.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1675db48490e5fa0b300f6329ecb8a9a37c29b9ab64fa9c964d34111788ca2d"},
{file = "coverage-6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7eed8459a2b81848cafb3280b39d7d49950d5f98e403677941c752e7e7ee47cb"},
{file = "coverage-6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b4285fde5286b946835a1a53bba3ad41ef74285ba9e8013e14b5ea93deaeafc"},
{file = "coverage-6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4748349734110fd32d46ff8897b561e6300d8989a494ad5a0a2e4f0ca974fc7"},
{file = "coverage-6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:823f9325283dc9565ba0aa2d240471a93ca8999861779b2b6c7aded45b58ee0f"},
{file = "coverage-6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fff16a30fdf57b214778eff86391301c4509e327a65b877862f7c929f10a4253"},
{file = "coverage-6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:da1a428bdbe71f9a8c270c7baab29e9552ac9d0e0cba5e7e9a4c9ee6465d258d"},
{file = "coverage-6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7d82c610a2e10372e128023c5baf9ce3d270f3029fe7274ff5bc2897c68f1318"},
{file = "coverage-6.3-cp37-cp37m-win32.whl", hash = "sha256:11e61c5548ecf74ea1f8b059730b049871f0e32b74f88bd0d670c20c819ad749"},
{file = "coverage-6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0c3525b1a182c8ffc9bca7e56b521e0c2b8b3e82f033c8e16d6d721f1b54d6"},
{file = "coverage-6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a189036c50dcd56100746139a459f0d27540fef95b09aba03e786540b8feaa5f"},
{file = "coverage-6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32168001f33025fd756884d56d01adebb34e6c8c0b3395ca8584cdcee9c7c9d2"},
{file = "coverage-6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5d79c9af3f410a2b5acad91258b4ae179ee9c83897eb9de69151b179b0227f5"},
{file = "coverage-6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85c5fc9029043cf8b07f73fbb0a7ab6d3b717510c3b5642b77058ea55d7cacde"},
{file = "coverage-6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7596aa2f2b8fa5604129cfc9a27ad9beec0a96f18078cb424d029fdd707468d"},
{file = "coverage-6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ce443a3e6df90d692c38762f108fc4c88314bf477689f04de76b3f252e7a351c"},
{file = "coverage-6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:012157499ec4f135fc36cd2177e3d1a1840af9b236cbe80e9a5ccfc83d912a69"},
{file = "coverage-6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a34d313105cdd0d3644c56df2d743fe467270d6ab93b5d4a347eb9fec8924d6"},
{file = "coverage-6.3-cp38-cp38-win32.whl", hash = "sha256:6e78b1e25e5c5695dea012be473e442f7094d066925604be20b30713dbd47f89"},
{file = "coverage-6.3-cp38-cp38-win_amd64.whl", hash = "sha256:433b99f7b0613bdcdc0b00cc3d39ed6d756797e3b078d2c43f8a38288520aec6"},
{file = "coverage-6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ed3244b415725f08ca3bdf02ed681089fd95e9465099a21c8e2d9c5d6ca2606"},
{file = "coverage-6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab4fc4b866b279740e0d917402f0e9a08683e002f43fa408e9655818ed392196"},
{file = "coverage-6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8582e9280f8d0f38114fe95a92ae8d0790b56b099d728cc4f8a2e14b1c4a18c"},
{file = "coverage-6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c72bb4679283c6737f452eeb9b2a0e570acaef2197ad255fb20162adc80bea76"},
{file = "coverage-6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca29c352389ea27a24c79acd117abdd8a865c6eb01576b6f0990cd9a4e9c9f48"},
{file = "coverage-6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:152cc2624381df4e4e604e21bd8e95eb8059535f7b768c1fb8b8ae0b26f47ab0"},
{file = "coverage-6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:51372e24b1f7143ee2df6b45cff6a721f3abe93b1e506196f3ffa4155c2497f7"},
{file = "coverage-6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72d9d186508325a456475dd05b1756f9a204c7086b07fffb227ef8cee03b1dc2"},
{file = "coverage-6.3-cp39-cp39-win32.whl", hash = "sha256:649df3641eb351cdfd0d5533c92fc9df507b6b2bf48a7ef8c71ab63cbc7b5c3c"},
{file = "coverage-6.3-cp39-cp39-win_amd64.whl", hash = "sha256:e67ccd53da5958ea1ec833a160b96357f90859c220a00150de011b787c27b98d"},
{file = "coverage-6.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:27ac7cb84538e278e07569ceaaa6f807a029dc194b1c819a9820b9bb5dbf63ab"},
{file = "coverage-6.3.tar.gz", hash = "sha256:987a84ff98a309994ca77ed3cc4b92424f824278e48e4bf7d1bb79a63cfe2099"},
]
distlib = [
{file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
{file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
{file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
{file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
]
filelock = [
{file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
{file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
{file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
]
ghp-import = [
{file = "ghp-import-2.0.1.tar.gz", hash = "sha256:753de2eace6e0f7d4edfb3cce5e3c3b98cd52aadb80163303d1d036bda7b4483"},
{file = "ghp-import-2.0.2.tar.gz", hash = "sha256:947b3771f11be850c852c64b561c600fdddf794bab363060854c1ee7ad05e071"},
{file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"},
]
identify = [
{file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"},
{file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"},
{file = "identify-2.4.6-py2.py3-none-any.whl", hash = "sha256:cf06b1639e0dca0c184b1504d8b73448c99a68e004a80524c7923b95f7b6837c"},
{file = "identify-2.4.6.tar.gz", hash = "sha256:233679e3f61a02015d4293dbccf16aa0e4996f868bd114688b8c124f18826706"},
]
importlib-metadata = [
{file = "importlib_metadata-4.6.4-py3-none-any.whl", hash = "sha256:ed5157fef23a4bc4594615a0dd8eba94b2bb36bf2a343fa3d8bb2fa0a62a99d5"},
{file = "importlib_metadata-4.6.4.tar.gz", hash = "sha256:7b30a78db2922d78a6f47fb30683156a14f3c6aa5cc23f77cc8967e9ab2d002f"},
{file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"},
{file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
jinja2 = [
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
{file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
]
markdown = [
{file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
{file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"},
{file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"},
{file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"},
]
markupsafe = [
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
@@ -814,12 +792,12 @@ mkdocs-autorefs = [
{file = "mkdocs_autorefs-0.2.1-py3-none-any.whl", hash = "sha256:f301b983a34259df90b3fcf7edc234b5e6c7065bd578781e66fd90b8cfbe76be"},
]
mkdocs-material = [
{file = "mkdocs-material-7.2.5.tar.gz", hash = "sha256:e2a3aa5e20fbdb260d22ec56c01247896c6ae743702e1cd9023fd149a4ae9890"},
{file = "mkdocs_material-7.2.5-py2.py3-none-any.whl", hash = "sha256:332bafc1584d2d229aa05f7894b4b0f62055fc0d05c96e6ef1785c86ef6e8f91"},
{file = "mkdocs-material-7.3.0.tar.gz", hash = "sha256:07db0580fa96c3473aee99ec3fb4606a1a5a1e4f4467e64c0cd1ba8da5b6476e"},
{file = "mkdocs_material-7.3.0-py2.py3-none-any.whl", hash = "sha256:b183c27dc0f44e631bbc32c51057f61a3e2ba8b3c1080e59f944167eeba9ff1d"},
]
mkdocs-material-extensions = [
{file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"},
{file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"},
{file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
{file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"},
]
mkdocstrings = [
{file = "mkdocstrings-0.15.2-py3-none-any.whl", hash = "sha256:8d6cbe64c07ae66739010979ca01d49dd2f64d1a45009f089d217b9cd2a65e36"},
@@ -859,44 +837,44 @@ nodeenv = [
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
]
packaging = [
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
{file = "platformdirs-2.2.0-py3-none-any.whl", hash = "sha256:4666d822218db6a262bdfdc9c39d21f23b4cfdb08af331a81e92751daf6c866c"},
{file = "platformdirs-2.2.0.tar.gz", hash = "sha256:632daad3ab546bd8e6af0537d09805cec458dce201bccfe23012df73332e181e"},
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
pre-commit = [
{file = "pre_commit-2.14.0-py2.py3-none-any.whl", hash = "sha256:ec3045ae62e1aa2eecfb8e86fa3025c2e3698f77394ef8d2011ce0aedd85b2d4"},
{file = "pre_commit-2.14.0.tar.gz", hash = "sha256:2386eeb4cf6633712c7cc9ede83684d53c8cafca6b59f79c738098b51c6d206c"},
{file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"},
{file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"},
]
py = [
{file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
{file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pygments = [
{file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"},
{file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"},
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
]
pymdown-extensions = [
{file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"},
{file = "pymdown_extensions-8.2-py3-none-any.whl", hash = "sha256:141452d8ed61165518f2c923454bf054866b85cf466feedb0eb68f04acdc2560"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
]
pytest = [
{file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"},
{file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"},
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
{file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
]
pytest-cov = [
{file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
@@ -911,43 +889,47 @@ pytkdocs = [
{file = "pytkdocs-0.11.1.tar.gz", hash = "sha256:1ec7e028fe8361acc1ce909ada4e6beabec28ef31e629618549109e1d58549f0"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
{file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
{file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
{file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
{file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
{file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
{file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
{file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
{file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
{file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
{file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
{file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
{file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
pyyaml-env-tag = [
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
]
rich = [
{file = "rich-10.16.1-py3-none-any.whl", hash = "sha256:bbe04dd6ac09e4b00d22cb1051aa127beaf6e16c3d8687b026e96d3fca6aad52"},
{file = "rich-10.16.1.tar.gz", hash = "sha256:4949e73de321784ef6664ebbc854ac82b20ff60b2865097b93f3b9b41e30da27"},
{file = "rich-11.1.0-py3-none-any.whl", hash = "sha256:365ebcdbfb3aa8d4b0ed2490e0fbf7b886a39d14eb7ea5fb7aece950835e1eed"},
{file = "rich-11.1.0.tar.gz", hash = "sha256:43e03d8eec12e21beaecc22c828a41c4247356414a12d5879834863d4ad53816"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -999,35 +981,35 @@ typing-extensions = [
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
virtualenv = [
{file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"},
{file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"},
{file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"},
{file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"},
]
watchdog = [
{file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f57ce4f7e498278fb2a091f39359930144a0f2f90ea8cbf4523c4e25de34028"},
{file = "watchdog-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b74d0d92a69a7ab5f101f9fe74e44ba017be269efa824337366ccbb4effde85"},
{file = "watchdog-2.1.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59767f476cd1f48531bf378f0300565d879688c82da8369ca8c52f633299523c"},
{file = "watchdog-2.1.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:814d396859c95598f7576d15bc257c3bd3ba61fa4bc1db7dfc18f09070ded7da"},
{file = "watchdog-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28777dbed3bbd95f9c70f461443990a36c07dbf49ae7cd69932cdd1b8fb2850c"},
{file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5cf78f794c9d7bc64a626ef4f71aff88f57a7ae288e0b359a9c6ea711a41395f"},
{file = "watchdog-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43bf728eb7830559f329864ab5da2302c15b2efbac24ad84ccc09949ba753c40"},
{file = "watchdog-2.1.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7053d4d22dc95c5e0c90aeeae1e4ed5269d2f04001798eec43a654a03008d22"},
{file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6f3ad1d973fe8fc8fe64ba38f6a934b74346342fa98ef08ad5da361a05d46044"},
{file = "watchdog-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41d44ef21a77a32b55ce9bf59b75777063751f688de51098859b7c7f6466589a"},
{file = "watchdog-2.1.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed4ca4351cd2bb0d863ee737a2011ca44d8d8be19b43509bd4507f8a449b376b"},
{file = "watchdog-2.1.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8874d5ad6b7f43b18935d9b0183e29727a623a216693d6938d07dfd411ba462f"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:50a7f81f99d238f72185f481b493f9de80096e046935b60ea78e1276f3d76960"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e40e33a4889382824846b4baa05634e1365b47c6fa40071dc2d06b4d7c715fc1"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_i686.whl", hash = "sha256:78b1514067ff4089f4dac930b043a142997a5b98553120919005e97fbaba6546"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64.whl", hash = "sha256:58ae842300cbfe5e62fb068c83901abe76e4f413234b7bec5446e4275eb1f9cb"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:b0cc7d8b7d60da6c313779d85903ce39a63d89d866014b085f720a083d5f3e9a"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_s390x.whl", hash = "sha256:e60d3bb7166b7cb830b86938d1eb0e6cfe23dfd634cce05c128f8f9967895193"},
{file = "watchdog-2.1.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:51af09ae937ada0e9a10cc16988ec03c649754a91526170b6839b89fc56d6acb"},
{file = "watchdog-2.1.5-py3-none-win32.whl", hash = "sha256:9391003635aa783957b9b11175d9802d3272ed67e69ef2e3394c0b6d9d24fa9a"},
{file = "watchdog-2.1.5-py3-none-win_amd64.whl", hash = "sha256:eab14adfc417c2c983fbcb2c73ef3f28ba6990d1fff45d1180bf7e38bda0d98d"},
{file = "watchdog-2.1.5-py3-none-win_ia64.whl", hash = "sha256:a2888a788893c4ef7e562861ec5433875b7915f930a5a7ed3d32c048158f1be5"},
{file = "watchdog-2.1.5.tar.gz", hash = "sha256:5563b005907613430ef3d4aaac9c78600dd5704e84764cb6deda4b3d72807f09"},
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"},
{file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"},
{file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"},
{file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"},
{file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"},
{file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"},
{file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"},
{file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"},
{file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"},
{file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"},
{file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"},
{file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"},
{file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"},
{file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"},
{file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"},
{file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"},
{file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"},
]
zipp = [
{file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"},
{file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"},
{file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
{file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
]

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.1.13"
version = "0.1.15"
homepage = "https://github.com/willmcgugan/textual"
description = "Text User Interface using Rich"
authors = ["Will McGugan <willmcgugan@gmail.com>"]
@@ -19,7 +19,8 @@ classifiers = [
[tool.poetry.dependencies]
python = "^3.7"
rich = "^10.12.0"
rich = "^11.0.0"
#rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"}
typing-extensions = { version = "^3.10.0", python = "<3.8" }

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
from typing import Literal
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
from rich.segment import Segment
@@ -14,6 +16,8 @@ BOX_STYLES: dict[str, tuple[str, str, str]] = {
"outer": ("▛▀▜", "▌ ▐", "▙▄▟"),
}
BoxType = Literal["", "rounded", "solid", "double", "dashed", "heavy", "inner", "outer"]
class Box:
def __init__(

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import os
import asyncio
import platform
import warnings
from typing import Any, Callable, Iterable, Type, TypeVar
@@ -21,7 +21,6 @@ from ._animator import Animator
from ._callback import invoke
from ._context import active_app
from ._event_broker import extract_handler_actions, NoHandler
from ._linux_driver import LinuxDriver
from ._profile import timer
from .binding import Bindings, NoBinding
from .css.stylesheet import Stylesheet, StylesheetParseError, StylesheetError
@@ -36,6 +35,9 @@ from .view import View
from .views import DockView
from .widget import Widget
PLATFORM = platform.system()
WINDOWS = PLATFORM == "Windows"
# asyncio will warn against resources not being cleared
warnings.simplefilter("always", ResourceWarning)
@@ -67,7 +69,6 @@ class App(DOMNode):
def __init__(
self,
console: Console | None = None,
screen: bool = True,
driver_class: Type[Driver] | None = None,
log: str = "",
@@ -85,10 +86,10 @@ class App(DOMNode):
driver_class (Type[Driver], optional): Driver class, or None to use default. Defaults to None.
title (str, optional): Title of the application. Defaults to "Textual Application".
"""
self.console = console or Console()
self.console = Console()
self.error_console = Console(stderr=True)
self._screen = screen
self.driver_class = driver_class or LinuxDriver
self.driver_class = driver_class or self.get_driver_class()
self._title = title
self._view_stack: list[View] = []
@@ -131,6 +132,24 @@ class App(DOMNode):
sub_title: Reactive[str] = Reactive("")
background: Reactive[str] = Reactive("black")
def get_driver_class(self) -> Type[Driver]:
"""Get a driver class for this platform.
Called by the constructor.
Returns:
Driver: A Driver class which manages input and display.
"""
if WINDOWS:
from .drivers.windows_driver import WindowsDriver
driver_class = WindowsDriver
else:
from .drivers.linux_driver import LinuxDriver
driver_class = LinuxDriver
return driver_class
def __rich_repr__(self) -> rich.repr.Result:
yield "title", self.title
@@ -211,7 +230,7 @@ class App(DOMNode):
"""
async def run_app() -> None:
app = cls(console=console, screen=screen, driver_class=driver, **kwargs)
app = cls(screen=screen, driver_class=driver, **kwargs)
await app.process_messages()
asyncio.run(run_app())
@@ -359,6 +378,7 @@ class App(DOMNode):
mount_event = events.Mount(sender=self)
await self.dispatch_message(mount_event)
self.console = Console()
self.title = self._title
self.refresh()
await self.animator.start()
@@ -432,7 +452,9 @@ class App(DOMNode):
await self.close_messages()
def refresh(self) -> None:
sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal"
sync_available = (
os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal" and not WINDOWS
)
if not self._closed:
console = self.console
try:

View File

@@ -1,5 +1,5 @@
"""
Style properties are descriptors which allow the Styles object to accept different types when
Style properties are descriptors which allow the ``Styles`` object to accept different types when
setting attributes. This gives the developer more freedom in how to express style information.
Descriptors also play nicely with Mypy, which is aware that attributes can have different types
@@ -33,10 +33,13 @@ if TYPE_CHECKING:
from ..layout import Layout
from .styles import Styles
from .styles import DockGroup
from .._box import BoxType
from ..layouts.factory import LayoutName
class ScalarProperty:
"""Descriptor for getting and setting scalar properties. Scalars are numeric values with a unit, e.g. "50vh"."""
def __init__(
self, units: set[Unit] | None = None, percent_unit: Unit = Unit.WIDTH
) -> None:
@@ -51,12 +54,34 @@ class ScalarProperty:
def __get__(
self, obj: Styles, objtype: type[Styles] | None = None
) -> Scalar | None:
"""Get the scalar property
Args:
obj (Styles): The ``Styles`` object
objtype (type[Styles]): The ``Styles`` class
Returns:
The Scalar object or ``None`` if it's not set.
"""
value = getattr(obj, self.internal_name)
return value
def __set__(
self, obj: Styles, value: float | Scalar | str | None
) -> float | Scalar | str | None:
def __set__(self, obj: Styles, value: float | Scalar | str | None) -> None:
"""Set the scalar property
Args:
obj (Styles): The ``Styles`` object.
value (float | Scalar | str | None): The value to set the scalar property to.
You can directly pass a float value, which will be interpreted with
a default unit of Cells. You may also provide a string such as ``"50%"``,
as you might do when writing CSS. If a string with no units is supplied,
Cells will be used as the unit. Alternatively, you can directly supply
a ``Scalar`` object.
Raises:
StyleValueError: If the value is of an invalid type, uses an invalid unit, or
cannot be parsed for any other reason.
"""
if value is None:
new_value = None
elif isinstance(value, float):
@@ -78,10 +103,13 @@ class ScalarProperty:
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
setattr(obj, self.internal_name, new_value)
obj.refresh()
return value
class BoxProperty:
"""Descriptor for getting and setting outlines and borders along a single edge.
For example "border-right", "outline-bottom", etc.
"""
DEFAULT = ("", Style())
def __set_name__(self, owner: Styles, name: str) -> None:
@@ -92,13 +120,32 @@ class BoxProperty:
def __get__(
self, obj: Styles, objtype: type[Styles] | None = None
) -> tuple[str, Style]:
) -> tuple[BoxType, Style]:
"""Get the box property
Args:
obj (Styles): The ``Styles`` object
objtype (type[Styles]): The ``Styles`` class
Returns:
A ``tuple[BoxType, Style]`` containing the string type of the box and
it's style. Example types are "rounded", "solid", and "dashed".
"""
value = getattr(obj, self.internal_name)
return value or self.DEFAULT
def __set__(
self, obj: Styles, border: tuple[str, str | Color | Style] | None
) -> tuple[str, str | Color | Style] | None:
def __set__(self, obj: Styles, border: tuple[BoxType, str | Color | Style] | None):
"""Set the box property
Args:
obj (Styles): The ``Styles`` object.
value (tuple[BoxType, str | Color | Style], optional): A 2-tuple containing the type of box to use,
e.g. "dashed", and the ``Style`` to be used. You can supply the ``Style`` directly, or pass a
``str`` (e.g. ``"blue on #f0f0f0"`` ) or ``Color`` instead.
Raises:
StyleSyntaxError: If the string supplied for the color has invalid syntax.
"""
if border is None:
new_value = None
else:
@@ -111,17 +158,16 @@ class BoxProperty:
new_value = (_type, Style.from_color(Color.parse(color)))
setattr(obj, self.internal_name, new_value)
obj.refresh()
return border
@rich.repr.auto
class Edges(NamedTuple):
"""Stores edges for border / outline."""
top: tuple[str, Style]
right: tuple[str, Style]
bottom: tuple[str, Style]
left: tuple[str, Style]
top: tuple[BoxType, Style]
right: tuple[BoxType, Style]
bottom: tuple[BoxType, Style]
left: tuple[BoxType, Style]
def __rich_repr__(self) -> rich.repr.Result:
top, right, bottom, left = self
@@ -150,6 +196,8 @@ class Edges(NamedTuple):
class BorderProperty:
"""Descriptor for getting and setting full borders and outlines."""
def __set_name__(self, owner: Styles, name: str) -> None:
self._properties = (
f"{name}_top",
@@ -159,6 +207,15 @@ class BorderProperty:
)
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Edges:
"""Get the border
Args:
obj (Styles): The ``Styles`` object
objtype (type[Styles]): The ``Styles`` class
Returns:
An ``Edges`` object describing the type and style of each edge.
"""
top, right, bottom, left = self._properties
border = Edges(
getattr(obj, top),
@@ -171,10 +228,25 @@ class BorderProperty:
def __set__(
self,
obj: Styles,
border: Sequence[tuple[str, str | Color | Style] | None]
| tuple[str, str | Color | Style]
border: Sequence[tuple[BoxType, str | Color | Style] | None]
| tuple[BoxType, str | Color | Style]
| None,
) -> None:
"""Set the border
Args:
obj (Styles): The ``Styles`` object.
border (Sequence[tuple[BoxType, str | Color | Style] | None] | tuple[BoxType, str | Color | Style] | None):
A ``tuple[BoxType, str | Color | Style]`` representing the type of box to use and the ``Style`` to apply
to the box.
Alternatively, you can supply a sequence of these tuples and they will be applied per-edge.
If the sequence is of length 1, all edges will be decorated according to the single element.
If the sequence is length 2, the first ``tuple`` will be applied to the top and bottom edges.
If the sequence is length 4, the tuples will be applied to the edges in the order: top, right, bottom, left.
Raises:
StyleValueError: When the supplied ``tuple`` is not of valid length (1, 2, or 4).
"""
top, right, bottom, left = self._properties
obj.refresh()
if border is None:
@@ -213,16 +285,25 @@ class BorderProperty:
class StyleProperty:
"""Descriptor for getting and setting full borders and outlines."""
DEFAULT_STYLE = Style()
def __set_name__(self, owner: Styles, name: str) -> None:
self._color_name = f"_rule_{name}_color"
self._bgcolor_name = f"_rule_{name}_background"
self._style_name = f"_rule_{name}_style"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
"""Get the Style
Args:
obj (Styles): The ``Styles`` object
objtype (type[Styles]): The ``Styles`` class
Returns:
A ``Style`` object.
"""
color = getattr(obj, self._color_name)
bgcolor = getattr(obj, self._bgcolor_name)
style = Style.from_color(color, bgcolor)
@@ -231,7 +312,17 @@ class StyleProperty:
style += style_flags
return style
def __set__(self, obj: Styles, style: Style | str | None) -> Style | str | None:
def __set__(self, obj: Styles, style: Style | str | None):
"""Set the Style
Args:
obj (Styles): The ``Styles`` object.
style (Style | str, optional): You can supply the ``Style`` directly, or a
string (e.g. ``"blue on #f0f0f0"``).
Raises:
StyleSyntaxError: When the supplied style string has invalid syntax.
"""
obj.refresh()
if style is None:
setattr(obj, self._color_name, None)
@@ -246,48 +337,104 @@ class StyleProperty:
setattr(obj, self._color_name, new_style.color)
setattr(obj, self._bgcolor_name, new_style.bgcolor)
setattr(obj, self._style_name, new_style.without_color)
return style
class SpacingProperty:
"""Descriptor for getting and setting spacing properties (e.g. padding and margin)."""
def __set_name__(self, owner: Styles, name: str) -> None:
self._internal_name = f"_rule_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Spacing:
"""Get the Spacing
Args:
obj (Styles): The ``Styles`` object
objtype (type[Styles]): The ``Styles`` class
Returns:
Spacing: The Spacing. If unset, returns the null spacing ``(0, 0, 0, 0)``.
"""
return getattr(obj, self._internal_name) or NULL_SPACING
def __set__(self, obj: Styles, spacing: SpacingDimensions) -> Spacing:
def __set__(self, obj: Styles, spacing: SpacingDimensions):
"""Set the Spacing
Args:
obj (Styles): The ``Styles`` object.
style (Style | str, optional): You can supply the ``Style`` directly, or a
string (e.g. ``"blue on #f0f0f0"``).
Raises:
ValueError: When the value is malformed, e.g. a ``tuple`` with a length that is
not 1, 2, or 4.
"""
obj.refresh(layout=True)
spacing = Spacing.unpack(spacing)
setattr(obj, self._internal_name, spacing)
return spacing
class DocksProperty:
"""Descriptor for getting and setting the docks property. This property
is used to define docks and their location on screen.
"""
def __get__(
self, obj: Styles, objtype: type[Styles] | None = None
) -> tuple[DockGroup, ...]:
"""Get the Docks property
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
tuple[DockGroup, ...]: A ``tuple`` containing the defined docks.
"""
return obj._rule_docks or ()
def __set__(
self, obj: Styles, docks: Iterable[DockGroup] | None
) -> Iterable[DockGroup] | None:
def __set__(self, obj: Styles, docks: Iterable[DockGroup] | None):
"""Set the Docks property
Args:
obj (Styles): The ``Styles`` object.
docks (Iterable[DockGroup]): Iterable of DockGroups
"""
obj.refresh(layout=True)
if docks is None:
obj._rule_docks = None
else:
obj._rule_docks = tuple(docks)
return docks
class DockProperty:
"""Descriptor for getting and setting the dock property. The dock property
allows you to specify which dock you wish a Widget to be attached to. This
should be used in conjunction with the "docks" property which lets you define
the docks themselves, and where they are located on screen.
"""
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
"""Get the Dock property
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
str: The dock name as a string, or "" if the rule is not set.
"""
return obj._rule_dock or ""
def __set__(self, obj: Styles, spacing: str | None) -> str | None:
def __set__(self, obj: Styles, spacing: str | None):
"""Set the Dock property
Args:
obj (Styles): The ``Styles`` object
spacing (str | None): The spacing to use.
"""
obj.refresh(layout=True)
obj._rule_dock = spacing
return spacing
class LayoutProperty:
@@ -324,17 +471,43 @@ class LayoutProperty:
class OffsetProperty:
"""Descriptor for getting and setting the offset property.
Offset consists of two values, x and y, that a widget's position
will be adjusted by before it is rendered.
"""
def __set_name__(self, owner: Styles, name: str) -> None:
self._internal_name = f"_rule_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> ScalarOffset:
"""Get the offset
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
ScalarOffset: The ``ScalarOffset`` indicating the adjustment that
will be made to widget position prior to it being rendered.
"""
return getattr(obj, self._internal_name) or ScalarOffset(
Scalar.from_number(0), Scalar.from_number(0)
)
def __set__(
self, obj: Styles, offset: tuple[int | str, int | str] | ScalarOffset
) -> tuple[int | str, int | str] | ScalarOffset:
def __set__(self, obj: Styles, offset: tuple[int | str, int | str] | ScalarOffset):
"""Set the offset
Args:
obj: The ``Styles`` class
offset: A ScalarOffset object, or a 2-tuple of the form ``(x, y)`` indicating
the x and y offsets. When the ``tuple`` form is used, x and y can be specified
as either ``int`` or ``str``. The string format allows you to also specify
any valid scalar unit e.g. ``("0.5vw", "0.5vh")``.
Raises:
ScalarParseError: If any of the string values supplied in the 2-tuple cannot
be parsed into a Scalar. For example, if you specify an non-existent unit.
"""
obj.refresh(layout=True)
if isinstance(offset, ScalarOffset):
setattr(obj, self._internal_name, offset)
@@ -352,26 +525,48 @@ class OffsetProperty:
)
_offset = ScalarOffset(scalar_x, scalar_y)
setattr(obj, self._internal_name, _offset)
return offset
class IntegerProperty:
"""Descriptor for getting and setting integer properties"""
def __set_name__(self, owner: Styles, name: str) -> None:
self._name = name
self._internal_name = f"_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> int:
"""Get the integer property, or the default ``0`` if not set.
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
int: The integer property value
"""
return getattr(obj, self._internal_name, 0)
def __set__(self, obj: Styles, value: int | None) -> int | None:
def __set__(self, obj: Styles, value: int):
"""Set the integer property
Args:
obj: The ``Styles`` object
value: The value to set the integer to
Raises:
StyleTypeError: If the supplied value is not an integer.
"""
obj.refresh()
if not isinstance(value, int):
raise StyleTypeError(f"{self._name} must be a str")
raise StyleTypeError(f"{self._name} must be an integer")
setattr(obj, self._internal_name, value)
return value
class StringProperty:
class StringEnumProperty:
"""Descriptor for getting and setting string properties and ensuring that the set
value belongs in the set of valid values.
"""
def __init__(self, valid_values: set[str], default: str) -> None:
self._valid_values = valid_values
self._default = default
@@ -381,9 +576,27 @@ class StringProperty:
self._internal_name = f"_rule_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> str:
"""Get the string property, or the default value if it's not set
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
str: The string property value
"""
return getattr(obj, self._internal_name, None) or self._default
def __set__(self, obj: Styles, value: str | None = None) -> str | None:
def __set__(self, obj: Styles, value: str | None = None):
"""Set the string property and ensure it is in the set of allowed values.
Args:
obj (Styles): The ``Styles`` object
value (str, optional): The string value to set the property to.
Raises:
StyleValueError: If the value is not in the set of valid values.
"""
obj.refresh()
if value is not None:
if value not in self._valid_values:
@@ -391,23 +604,41 @@ class StringProperty:
f"{self._name} must be one of {friendly_list(self._valid_values)}"
)
setattr(obj, self._internal_name, value)
return value
class NameProperty:
"""Descriptor for getting and setting name properties."""
def __set_name__(self, owner: Styles, name: str) -> None:
self._name = name
self._internal_name = f"_rule_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None) -> str:
"""Get the name property
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
str: The name
"""
return getattr(obj, self._internal_name) or ""
def __set__(self, obj: Styles, name: str | None) -> str | None:
def __set__(self, obj: Styles, name: str | None):
"""Set the name property
Args:
obj: The ``Styles`` object
name: The name to set the property to
Raises:
StyleTypeError: If the value is not a ``str``.
"""
obj.refresh(layout=True)
if not isinstance(name, str):
raise StyleTypeError(f"{self._name} must be a str")
setattr(obj, self._internal_name, name)
return name
class NameListProperty:
@@ -436,14 +667,36 @@ class NameListProperty:
class ColorProperty:
"""Descriptor for getting and setting color properties."""
def __set_name__(self, owner: Styles, name: str) -> None:
self._name = name
self._internal_name = f"_rule_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Color:
"""Get the ``Color``, or ``Color.default()`` if no color is set.
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
Color: The Color
"""
return getattr(obj, self._internal_name, None) or Color.default()
def __set__(self, obj: Styles, color: Color | str | None) -> Color | str | None:
def __set__(self, obj: Styles, color: Color | str | None):
"""Set the Color
Args:
obj (Styles): The ``Styles`` object
color (Color | str | None): The color to set. Pass a ``Color`` instance directly,
or pass a ``str`` which will be parsed into a color (e.g. ``"red""``, ``"rgb(20, 50, 80)"``,
``"#f4e32d"``).
Raises:
ColorParseError: When the color string is invalid.
"""
obj.refresh()
if color is None:
setattr(self, self._internal_name, None)
@@ -453,10 +706,11 @@ class ColorProperty:
elif isinstance(color, str):
new_color = Color.parse(color)
setattr(self, self._internal_name, new_color)
return color
class StyleFlagsProperty:
"""Descriptor for getting and set style flag properties (e.g. ``bold italic underline``)."""
_VALID_PROPERTIES = {
"not",
"bold",
@@ -475,9 +729,28 @@ class StyleFlagsProperty:
self._internal_name = f"_rule_{name}"
def __get__(self, obj: Styles, objtype: type[Styles] | None = None) -> Style:
"""Get the ``Style``
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
Style: The ``Style`` object
"""
return getattr(obj, self._internal_name, None) or Style.null()
def __set__(self, obj: Styles, style_flags: str | None) -> str | None:
def __set__(self, obj: Styles, style_flags: str | None):
"""Set the style using a style flag string
Args:
obj (Styles): The ``Styles`` object.
style_flags (str, optional): The style flags to set as a string. For example,
``"bold italic"``.
Raises:
StyleValueError: If the value is an invalid style flag
"""
obj.refresh()
if style_flags is None:
setattr(self, self._internal_name, None)
@@ -486,13 +759,17 @@ class StyleFlagsProperty:
valid_word = self._VALID_PROPERTIES.__contains__
for word in words:
if not valid_word(word):
raise StyleValueError(f"unknown word {word!r} in style flags")
raise StyleValueError(
f"unknown word {word!r} in style flags, "
f"valid values are {friendly_list(self._VALID_PROPERTIES)}"
)
style = Style.parse(style_flags)
setattr(obj, self._internal_name, style)
return style_flags
class TransitionsProperty:
"""Descriptor for getting transitions properties"""
def __set_name__(self, owner: Styles, name: str) -> None:
self._name = name
self._internal_name = f"_rule_{name}"
@@ -500,4 +777,15 @@ class TransitionsProperty:
def __get__(
self, obj: Styles, objtype: type[Styles] | None = None
) -> dict[str, Transition]:
"""Get a mapping of properties to the the transitions applied to them.
Args:
obj (Styles): The ``Styles`` object.
objtype (type[Styles]): The ``Styles`` class.
Returns:
dict[str, Transition]: A ``dict`` mapping property names to the ``Transition`` applied to them.
e.g. ``{"offset": Transition(...), ...}``. If no transitions have been set, an empty ``dict``
is returned.
"""
return getattr(obj, self._internal_name, None) or {}

View File

@@ -20,7 +20,7 @@ from ._style_properties import (
NameListProperty,
ScalarProperty,
SpacingProperty,
StringProperty,
StringEnumProperty,
StyleProperty,
StyleFlagsProperty,
TransitionsProperty,
@@ -34,6 +34,8 @@ from .scalar import Scalar, ScalarOffset, Unit
from .scalar_animation import ScalarAnimation
from .transition import Transition
from .types import Display, Edge, Visibility
from .types import Specificity3, Specificity4
from .. import log
from .._animator import Animation, EasingFunction
@@ -97,8 +99,8 @@ class Styles:
important: set[str] = field(default_factory=set)
display = StringProperty(VALID_DISPLAY, "block")
visibility = StringProperty(VALID_VISIBILITY, "visible")
display = StringEnumProperty(VALID_DISPLAY, "block")
visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
layout = LayoutProperty()
text = StyleProperty()

View File

@@ -14,9 +14,6 @@ if TYPE_CHECKING:
from rich.console import Console
WINDOWS = platform.system() == "Windows"
class Driver(ABC):
def __init__(self, console: "Console", target: "MessageTarget") -> None:
self.console = console

View File

View File

@@ -14,17 +14,19 @@ from threading import Event, Thread
if TYPE_CHECKING:
from rich.console import Console
from . import log
from .. import log
from . import events
from .driver import Driver
from .geometry import Size
from ._types import MessageTarget
from ._xterm_parser import XTermParser
from ._profile import timer
from .. import events
from ..driver import Driver
from ..geometry import Size
from .._types import MessageTarget
from .._xterm_parser import XTermParser
from .._profile import timer
class LinuxDriver(Driver):
"""Powers display and input for Linux / MacOS"""
def __init__(self, console: "Console", target: "MessageTarget") -> None:
super().__init__(console, target)
self.fileno = sys.stdin.fileno()
@@ -155,21 +157,17 @@ class LinuxDriver(Driver):
pass
def stop_application_mode(self) -> None:
self.disable_input()
with timer("disable_input"):
self.disable_input()
if self.attrs_before is not None:
try:
termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
except termios.error:
pass
with timer("tcsetattr"):
if self.attrs_before is not None:
try:
termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
except termios.error:
pass
with timer("set_alt_screen False, show cursor"):
with self.console:
self.console.set_alt_screen(False)
self.console.show_cursor(True)
with self.console:
self.console.set_alt_screen(False)
self.console.show_cursor(True)
def run_input_thread(self, loop) -> None:
try:
@@ -215,11 +213,11 @@ class LinuxDriver(Driver):
if __name__ == "__main__":
from time import sleep
from rich.console import Console
from . import events
from .. import events
console = Console()
from .app import App
from ..app import App
class MyApp(App):
async def on_mount(self, event: events.Mount) -> None:

View File

@@ -0,0 +1,289 @@
import ctypes
import msvcrt
import sys
import threading
from asyncio import AbstractEventLoop, run_coroutine_threadsafe
from ctypes import Structure, Union, byref, wintypes
from ctypes.wintypes import BOOL, CHAR, DWORD, HANDLE, SHORT, UINT, WCHAR, WORD
from typing import IO, Callable, List, Optional
from .._types import EventTarget
from .._xterm_parser import XTermParser
from ..events import Event, Resize
from ..geometry import Size
KERNEL32 = ctypes.WinDLL("kernel32", use_last_error=True)
# Console input modes
ENABLE_ECHO_INPUT = 0x0004
ENABLE_EXTENDED_FLAGS = 0x0080
ENABLE_INSERT_MODE = 0x0020
ENABLE_LINE_INPUT = 0x0002
ENABLE_MOUSE_INPUT = 0x0010
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_QUICK_EDIT_MODE = 0x0040
ENABLE_WINDOW_INPUT = 0x0008
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
# Console output modes
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
WAIT_TIMEOUT = 0x00000102
GetStdHandle = KERNEL32.GetStdHandle
GetStdHandle.argtypes = [wintypes.DWORD]
GetStdHandle.restype = wintypes.HANDLE
class COORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/coord-str"""
_fields_ = [
("X", SHORT),
("Y", SHORT),
]
class uChar(Union):
"""https://docs.microsoft.com/en-us/windows/console/key-event-record-str"""
_fields_ = [
("AsciiChar", CHAR),
("UnicodeChar", WCHAR),
]
class KEY_EVENT_RECORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/key-event-record-str"""
_fields_ = [
("bKeyDown", BOOL),
("wRepeatCount", WORD),
("wVirtualKeyCode", WORD),
("wVirtualScanCode", WORD),
("uChar", uChar),
("dwControlKeyState", DWORD),
]
class MOUSE_EVENT_RECORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str"""
_fields_ = [
("dwMousePosition", COORD),
("dwButtonState", DWORD),
("dwControlKeyState", DWORD),
("dwEventFlags", DWORD),
]
class WINDOW_BUFFER_SIZE_RECORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/window-buffer-size-record-str"""
_fields_ = [("dwSize", COORD)]
class MENU_EVENT_RECORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/menu-event-record-str"""
_fields_ = [("dwCommandId", UINT)]
class FOCUS_EVENT_RECORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/focus-event-record-str"""
_fields_ = [("bSetFocus", BOOL)]
class InputEvent(Union):
"""https://docs.microsoft.com/en-us/windows/console/input-record-str"""
_fields_ = [
("KeyEvent", KEY_EVENT_RECORD),
("MouseEvent", MOUSE_EVENT_RECORD),
("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
("MenuEvent", MENU_EVENT_RECORD),
("FocusEvent", FOCUS_EVENT_RECORD),
]
class INPUT_RECORD(Structure):
"""https://docs.microsoft.com/en-us/windows/console/input-record-str"""
_fields_ = [("EventType", wintypes.WORD), ("Event", InputEvent)]
def _set_console_mode(file: IO, mode: int) -> bool:
"""Set the console mode for a given file (stdout or stdin).
Args:
file (IO): A file like object.
mode (int): New mode.
Returns:
bool: True on success, otherwise False.
"""
windows_filehandle = msvcrt.get_osfhandle(file.fileno())
success = KERNEL32.SetConsoleMode(windows_filehandle, mode)
return success
def _get_console_mode(file: IO) -> int:
"""Get the console mode for a given file (stdout or stdin)
Args:
file (IO): A file-like object.
Returns:
int: The current console mode.
"""
windows_filehandle = msvcrt.get_osfhandle(file.fileno())
mode = wintypes.DWORD()
KERNEL32.GetConsoleMode(windows_filehandle, ctypes.byref(mode))
return mode.value
def enable_application_mode() -> Callable[[], None]:
"""Enable application mode.
Returns:
Callable[[], None]: A callable that will restore terminal to previous state.
"""
terminal_in = sys.stdin
terminal_out = sys.stdout
current_console_mode_in = _get_console_mode(terminal_in)
current_console_mode_out = _get_console_mode(terminal_out)
def restore() -> None:
"""Restore console mode to previous settings"""
_set_console_mode(terminal_in, current_console_mode_in)
_set_console_mode(terminal_out, current_console_mode_out)
_set_console_mode(
terminal_out, current_console_mode_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING
)
_set_console_mode(terminal_in, ENABLE_VIRTUAL_TERMINAL_INPUT)
return restore
def _wait_for_handles(handles: List[HANDLE], timeout: int = -1) -> Optional[HANDLE]:
"""
Waits for multiple handles. (Similar to 'select') Returns the handle which is ready.
Returns `None` on timeout.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx
Note that handles should be a list of `HANDLE` objects, not integers. See
this comment in the patch by @quark-zju for the reason why:
''' Make sure HANDLE on Windows has a correct size
Previously, the type of various HANDLEs are native Python integer
types. The ctypes library will treat them as 4-byte integer when used
in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually
a small integer. Depending on whether the extra 4 bytes are zero-ed out
or not, things can happen to work, or break. '''
This function returns either `None` or one of the given `HANDLE` objects.
(The return value can be tested with the `is` operator.)
"""
arrtype = HANDLE * len(handles)
handle_array = arrtype(*handles)
ret: int = KERNEL32.WaitForMultipleObjects(
len(handle_array), handle_array, BOOL(False), DWORD(timeout)
)
if ret == WAIT_TIMEOUT:
return None
else:
return handles[ret]
class EventMonitor(threading.Thread):
"""A thread to send key / window events to Textual loop."""
def __init__(
self,
loop: AbstractEventLoop,
app,
target: EventTarget,
exit_event: threading.Event,
process_event: Callable[[Event], None],
) -> None:
self.loop = loop
self.app = app
self.target = target
self.exit_event = exit_event
self.process_event = process_event
self.app.log("event monitor constructed")
super().__init__()
def run(self) -> None:
self.app.log("event monitor thread started")
exit_requested = self.exit_event.is_set
parser = XTermParser(self.target, lambda: False)
try:
read_count = wintypes.DWORD(0)
hIn = GetStdHandle(STD_INPUT_HANDLE)
MAX_EVENTS = 1024
KEY_EVENT = 0x0001
WINDOW_BUFFER_SIZE_EVENT = 0x0004
arrtype = INPUT_RECORD * MAX_EVENTS
input_records = arrtype()
ReadConsoleInputW = KERNEL32.ReadConsoleInputW
keys: List[str] = []
append_key = keys.append
while not exit_requested():
# Wait for new events
if _wait_for_handles([hIn], 200) is None:
# No new events
continue
# Get new events
ReadConsoleInputW(
hIn, byref(input_records), MAX_EVENTS, byref(read_count)
)
read_input_records = input_records[: read_count.value]
del keys[:]
new_size: Optional[tuple[int, int]] = None
for input_record in read_input_records:
event_type = input_record.EventType
if event_type == KEY_EVENT:
# Key event, store unicode char in keys list
key_event = input_record.Event.KeyEvent
key = key_event.uChar.UnicodeChar
if key_event.bKeyDown or key == "\x1b":
append_key(key)
elif event_type == WINDOW_BUFFER_SIZE_EVENT:
# Window size changed, store size
size = input_record.Event.WindowBufferSizeEvent.dwSize
new_size = (size.X, size.Y)
if keys:
# Process keys
for event in parser.feed("".join(keys)):
self.process_event(event)
if new_size is not None:
# Process changed size
self.on_size_change(*new_size)
except Exception as error:
self.app.log("EVENT MONITOR ERROR", error)
self.app.log("event monitor thread finished")
def on_size_change(self, width: int, height: int) -> None:
"""Called when terminal size changes."""
event = Resize(self.target, Size(width, height))
run_coroutine_threadsafe(self.target.post_message(event), loop=self.loop)

View File

@@ -0,0 +1,81 @@
from __future__ import annotations
import asyncio
import sys
from threading import Event, Thread
from typing import TYPE_CHECKING, Callable
from .._context import active_app
from .._types import MessageTarget
from ..driver import Driver
from . import win32
if TYPE_CHECKING:
from rich.console import Console
class WindowsDriver(Driver):
"""Powers display and input for Windows."""
def __init__(self, console: "Console", target: "MessageTarget") -> None:
super().__init__(console, target)
self.in_fileno = sys.stdin.fileno()
self.out_fileno = sys.stdout.fileno()
self.exit_event = Event()
self._event_thread: Thread | None = None
self._restore_console: Callable[[], None] | None = None
def _enable_mouse_support(self) -> None:
write = self.console.file.write
write("\x1b[?1000h") # SET_VT200_MOUSE
write("\x1b[?1003h") # SET_ANY_EVENT_MOUSE
write("\x1b[?1015h") # SET_VT200_HIGHLIGHT_MOUSE
write("\x1b[?1006h") # SET_SGR_EXT_MODE_MOUSE
self.console.file.flush()
def _disable_mouse_support(self) -> None:
write = self.console.file.write
write("\x1b[?1000l")
write("\x1b[?1003l")
write("\x1b[?1015l")
write("\x1b[?1006l")
self.console.file.flush()
def start_application_mode(self) -> None:
loop = asyncio.get_event_loop()
self._restore_console = win32.enable_application_mode()
self.console.set_alt_screen(True)
self._enable_mouse_support()
self.console.show_cursor(False)
self.console.file.write("\033[?1003h\n")
app = active_app.get()
self._event_thread = win32.EventMonitor(
loop, app, self._target, self.exit_event, self.process_event
)
self._event_thread.start()
def disable_input(self) -> None:
try:
if not self.exit_event.is_set():
self._disable_mouse_support()
self.exit_event.set()
if self._event_thread is not None:
self._event_thread.join()
self._event_thread = None
except Exception as error:
# TODO: log this
pass
def stop_application_mode(self) -> None:
self.disable_input()
if self._restore_console:
self._restore_console()
with self.console:
self.console.set_alt_screen(False)
self.console.show_cursor(True)

View File

@@ -237,7 +237,7 @@ class MouseEvent(InputEvent, bubble=True):
Args:
sender (MessageTarget): The sender of the event.
x (int): The relative x coordinate.
y (int): The relative y cootdinate.
y (int): The relative y coordinate.
delta_x (int): Change in x since the last message.
delta_y (int): Change in y since the last message.
button (int): Indexed of the pressed button.

View File

View File

@@ -0,0 +1,122 @@
from __future__ import annotations
from rich.console import ConsoleOptions, Console, RenderResult
from rich.segment import Segment
from rich.style import StyleType
class UnderlineBar:
"""Thin horizontal bar with a portion highlighted.
Args:
highlight_range (tuple[float, float]): The range to highlight. Defaults to ``(0, 0)`` (no highlight)
highlight_style (StyleType): The style of the highlighted range of the bar.
background_style (StyleType): The style of the non-highlighted range(s) of the bar.
width (int, optional): The width of the bar, or ``None`` to fill available width.
"""
def __init__(
self,
highlight_range: tuple[float, float] = (0, 0),
highlight_style: StyleType = "magenta",
background_style: StyleType = "grey37",
width: int | None = None,
) -> None:
self.highlight_range = highlight_range
self.highlight_style = highlight_style
self.background_style = background_style
self.width = width
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
highlight_style = console.get_style(self.highlight_style)
background_style = console.get_style(self.background_style)
half_bar_right = ""
half_bar_left = ""
bar = ""
width = self.width or options.max_width
start, end = self.highlight_range
start = max(start, 0)
end = min(end, width)
if start == end == 0 or end < 0 or start > end:
yield Segment(bar * width, style=background_style)
return
# Round start and end to nearest half
start = round(start * 2) / 2
end = round(end * 2) / 2
# Check if we start/end on a number that rounds to a .5
half_start = start - int(start) > 0
half_end = end - int(end) > 0
# Initial non-highlighted portion of bar
yield Segment(bar * (int(start - 0.5)), style=background_style)
if not half_start and start > 0:
yield Segment(half_bar_right, style=background_style)
# The highlighted portion
bar_width = int(end) - int(start)
if half_start:
yield Segment(half_bar_left + bar * (bar_width - 1), style=highlight_style)
else:
yield Segment(bar * bar_width, style=highlight_style)
if half_end:
yield Segment(half_bar_right, style=highlight_style)
# The non-highlighted tail
if not half_end and end - width != 0:
yield Segment(half_bar_left, style=background_style)
yield Segment(bar * (int(width) - int(end) - 1), style=background_style)
if __name__ == "__main__":
import random
from time import sleep
from rich.color import ANSI_COLOR_NAMES
console = Console()
def frange(start, end, step):
current = start
while current < end:
yield current
current += step
while current >= 0:
yield current
current -= step
step = 0.1
start_range = frange(0.5, 10.5, step)
end_range = frange(10, 20, step)
ranges = zip(start_range, end_range)
console.print(UnderlineBar(width=20), f" (.0, .0)")
for range in ranges:
color = random.choice(list(ANSI_COLOR_NAMES.keys()))
console.print(
UnderlineBar(
range,
highlight_style=color,
width=20,
),
f" {range}",
)
from rich.live import Live
bar = UnderlineBar(width=80, highlight_range=(0, 4.5))
with Live(bar, refresh_per_second=60) as live:
while True:
bar.highlight_range = (
bar.highlight_range[0] + 0.1,
bar.highlight_range[1] + 0.1,
)
sleep(0.005)

View File

View File

@@ -0,0 +1,126 @@
from tests.utilities.render import render
from textual.renderables.underline_bar import UnderlineBar
MAGENTA = "\x1b[35m"
GREY = "\x1b[38;5;59m"
STOP = "\x1b[0m"
GREEN = "\x1b[32m"
RED = "\x1b[31m"
def test_no_highlight():
bar = UnderlineBar(width=6)
assert render(bar) == f"{GREY}━━━━━━{STOP}"
def test_highlight_from_zero():
bar = UnderlineBar(highlight_range=(0, 2.5), width=6)
assert render(bar) == (
f"{MAGENTA}━━{STOP}{MAGENTA}{STOP}{GREY}━━━{STOP}"
)
def test_highlight_from_zero_point_five():
bar = UnderlineBar(highlight_range=(0.5, 2), width=6)
assert render(bar) == (
f"{MAGENTA}╺━{STOP}{GREY}{STOP}{GREY}━━━{STOP}"
)
def test_highlight_middle():
bar = UnderlineBar(highlight_range=(2, 4), width=6)
assert render(bar) == (
f"{GREY}{STOP}"
f"{GREY}{STOP}"
f"{MAGENTA}━━{STOP}"
f"{GREY}{STOP}"
f"{GREY}{STOP}"
)
def test_highlight_half_start():
bar = UnderlineBar(highlight_range=(2.5, 4), width=6)
assert render(bar) == (
f"{GREY}━━{STOP}"
f"{MAGENTA}╺━{STOP}"
f"{GREY}{STOP}"
f"{GREY}{STOP}"
)
def test_highlight_half_end():
bar = UnderlineBar(highlight_range=(2, 4.5), width=6)
assert render(bar) == (
f"{GREY}{STOP}"
f"{GREY}{STOP}"
f"{MAGENTA}━━{STOP}"
f"{MAGENTA}{STOP}"
f"{GREY}{STOP}"
)
def test_highlight_half_start_and_half_end():
bar = UnderlineBar(highlight_range=(2.5, 4.5), width=6)
assert render(bar) == (
f"{GREY}━━{STOP}"
f"{MAGENTA}╺━{STOP}"
f"{MAGENTA}{STOP}"
f"{GREY}{STOP}"
)
def test_highlight_to_near_end():
bar = UnderlineBar(highlight_range=(3, 5.5), width=6)
assert render(bar) == (
f"{GREY}━━{STOP}"
f"{GREY}{STOP}"
f"{MAGENTA}━━{STOP}"
f"{MAGENTA}{STOP}"
)
def test_highlight_to_end():
bar = UnderlineBar(highlight_range=(3, 6), width=6)
assert render(bar) == (
f"{GREY}━━{STOP}{GREY}{STOP}{MAGENTA}━━━{STOP}"
)
def test_highlight_out_of_bounds_start():
bar = UnderlineBar(highlight_range=(-2, 3), width=6)
assert render(bar) == (
f"{MAGENTA}━━━{STOP}{GREY}{STOP}{GREY}━━{STOP}"
)
def test_highlight_out_of_bounds_end():
bar = UnderlineBar(highlight_range=(3, 9), width=6)
assert render(bar) == (
f"{GREY}━━{STOP}{GREY}{STOP}{MAGENTA}━━━{STOP}"
)
def test_highlight_full_range_out_of_bounds_end():
bar = UnderlineBar(highlight_range=(9, 10), width=6)
assert render(bar) == f"{GREY}━━━━━━{STOP}"
def test_highlight_full_range_out_of_bounds_start():
bar = UnderlineBar(highlight_range=(-5, -2), width=6)
assert render(bar) == f"{GREY}━━━━━━{STOP}"
def test_custom_styles():
bar = UnderlineBar(
highlight_range=(2, 4),
highlight_style="red",
background_style="green",
width=6
)
assert render(bar) == (
f"{GREEN}{STOP}"
f"{GREEN}{STOP}"
f"{RED}━━{STOP}"
f"{GREEN}{STOP}"
f"{GREEN}{STOP}"
)

24
tests/utilities/render.py Normal file
View File

@@ -0,0 +1,24 @@
import io
import re
from rich.console import Console, RenderableType
re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
def replace_link_ids(render: str) -> str:
"""Link IDs have a random ID and system path which is a problem for
reproducible tests.
"""
return re_link_ids.sub("id=0;foo\x1b", render)
def render(renderable: RenderableType, no_wrap: bool = False) -> str:
console = Console(
width=100, file=io.StringIO(), color_system="truecolor", legacy_windows=False
)
console.print(renderable, no_wrap=no_wrap)
output = replace_link_ids(console.file.getvalue())
return output