mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'css' of github.com:willmcgugan/textual into style-error-improvements
This commit is contained in:
@@ -8,7 +8,7 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.8b0
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: ^tests/
|
||||
|
||||
239
poetry.lock
generated
239
poetry.lock
generated
@@ -85,7 +85,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.1.0"
|
||||
version = "22.3.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -96,7 +96,7 @@ click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = ">=1.1.0"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
||||
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||
|
||||
@@ -135,11 +135,11 @@ unicode_backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.4"
|
||||
version = "8.1.2"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
@@ -149,7 +149,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
name = "colorama"
|
||||
version = "0.4.4"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
@@ -240,7 +240,7 @@ python-versions = ">=3.5"
|
||||
name = "importlib-metadata"
|
||||
version = "4.11.3"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
@@ -263,11 +263,11 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.0.3"
|
||||
version = "3.1.1"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
@@ -307,7 +307,7 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.2.3"
|
||||
version = "1.3.0"
|
||||
description = "Project documentation with Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -316,8 +316,8 @@ python-versions = ">=3.6"
|
||||
[package.dependencies]
|
||||
click = ">=3.3"
|
||||
ghp-import = ">=1.0"
|
||||
importlib-metadata = ">=3.10"
|
||||
Jinja2 = ">=2.10.1"
|
||||
importlib-metadata = ">=4.3"
|
||||
Jinja2 = ">=2.10.2"
|
||||
Markdown = ">=3.2.1"
|
||||
mergedeep = ">=1.3.4"
|
||||
packaging = ">=20.5"
|
||||
@@ -444,15 +444,15 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
@@ -471,11 +471,11 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "2.17.0"
|
||||
version = "2.18.1"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
cfgv = ">=2.0.0"
|
||||
@@ -515,14 +515,14 @@ Markdown = ">=3.2"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.7"
|
||||
description = "Python parsing module"
|
||||
version = "3.0.8"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
@@ -641,7 +641,7 @@ pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "12.0.1"
|
||||
version = "12.1.0"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -650,7 +650,7 @@ python-versions = ">=3.6.2,<4.0.0"
|
||||
[package.dependencies]
|
||||
commonmark = ">=0.9.0,<0.10.0"
|
||||
pygments = ">=2.6.0,<3.0.0"
|
||||
typing-extensions = {version = ">=3.7.4,<5.0", markers = "python_version < \"3.8\""}
|
||||
typing-extensions = {version = ">=3.7.4,<5.0", markers = "python_version < \"3.9\""}
|
||||
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
|
||||
@@ -692,7 +692,7 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.5.2"
|
||||
version = "1.5.3"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -708,7 +708,7 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.13.4"
|
||||
version = "20.14.1"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -727,7 +727,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "2.1.6"
|
||||
version = "2.1.7"
|
||||
description = "Filesystem events monitoring"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -751,20 +751,20 @@ typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.7.0"
|
||||
version = "3.8.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
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"]
|
||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||
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 (>=0.9.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "24cdf0a574b7ae21c2ea5c407075b044b9cd01f7d2d01dd562e3cf33d958d88c"
|
||||
content-hash = "256c1d6571a11bf4b80d0eba16d9e39bf2965c4436281c3ec40033cca54aa098"
|
||||
|
||||
[metadata.files]
|
||||
aiohttp = [
|
||||
@@ -866,29 +866,29 @@ attrs = [
|
||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
|
||||
{file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
|
||||
{file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
|
||||
{file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
|
||||
{file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
|
||||
{file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
|
||||
{file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
|
||||
{file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
|
||||
{file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
|
||||
{file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
|
||||
{file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
|
||||
{file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
|
||||
{file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
|
||||
{file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
|
||||
{file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
|
||||
{file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
|
||||
{file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
|
||||
{file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
|
||||
{file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
|
||||
{file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
|
||||
{file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
|
||||
{file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
|
||||
{file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
|
||||
{file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
|
||||
{file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
|
||||
{file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
|
||||
{file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
|
||||
{file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
|
||||
{file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
|
||||
{file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
|
||||
{file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
|
||||
{file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
|
||||
{file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
|
||||
{file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
|
||||
{file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
|
||||
{file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
|
||||
{file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
|
||||
{file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
|
||||
{file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
|
||||
{file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
|
||||
{file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
|
||||
{file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
|
||||
{file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
|
||||
{file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
|
||||
{file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
|
||||
]
|
||||
cached-property = [
|
||||
{file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
|
||||
@@ -903,8 +903,8 @@ charset-normalizer = [
|
||||
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
|
||||
{file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
|
||||
{file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"},
|
||||
{file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
||||
@@ -1047,8 +1047,8 @@ iniconfig = [
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
|
||||
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
|
||||
{file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"},
|
||||
{file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"},
|
||||
]
|
||||
markdown = [
|
||||
{file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"},
|
||||
@@ -1101,8 +1101,8 @@ mergedeep = [
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
mkdocs = [
|
||||
{file = "mkdocs-1.2.3-py3-none-any.whl", hash = "sha256:a1fa8c2d0c1305d7fc2b9d9f607c71778572a8b110fb26642aa00296c9e6d072"},
|
||||
{file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"},
|
||||
{file = "mkdocs-1.3.0-py3-none-any.whl", hash = "sha256:26bd2b03d739ac57a3e6eed0b7bcc86168703b719c27b99ad6ca91dc439aacde"},
|
||||
{file = "mkdocs-1.3.0.tar.gz", hash = "sha256:b504405b04da38795fec9b2e5e28f6aa3a73bb0960cb6d5d27ead28952bd35ea"},
|
||||
]
|
||||
mkdocs-autorefs = [
|
||||
{file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
|
||||
@@ -1220,16 +1220,16 @@ pathspec = [
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"},
|
||||
{file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"},
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
]
|
||||
pluggy = [
|
||||
{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.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"},
|
||||
{file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"},
|
||||
{file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"},
|
||||
{file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
@@ -1244,8 +1244,8 @@ pymdown-extensions = [
|
||||
{file = "pymdown_extensions-9.3-py3-none-any.whl", hash = "sha256:b37461a181c1c8103cfe1660081726a0361a8294cbfda88e5b02cefe976f0546"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
||||
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
|
||||
{file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"},
|
||||
{file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
|
||||
@@ -1312,8 +1312,8 @@ pyyaml-env-tag = [
|
||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||
]
|
||||
rich = [
|
||||
{file = "rich-12.0.1-py3-none-any.whl", hash = "sha256:ce5c714e984a2d185399e4e1dd1f8b2feacb7cecfc576f1522425643a36a57ea"},
|
||||
{file = "rich-12.0.1.tar.gz", hash = "sha256:3fba9dd15ebe048e2795a02ac19baee79dc12cc50b074ef70f2958cd651b59a9"},
|
||||
{file = "rich-12.1.0-py3-none-any.whl", hash = "sha256:b60ff99f4ff7e3d1d37444dee2b22fdd941c622dbc37841823ec1ce7f058b263"},
|
||||
{file = "rich-12.1.0.tar.gz", hash = "sha256:198ae15807a7c1bf84ceabf662e902731bf8f874f9e775e2289cab02bb6a4e30"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
@@ -1370,30 +1370,30 @@ tomli = [
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"},
|
||||
{file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"},
|
||||
{file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"},
|
||||
{file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"},
|
||||
{file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"},
|
||||
{file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"},
|
||||
{file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"},
|
||||
{file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"},
|
||||
{file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"},
|
||||
{file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"},
|
||||
{file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"},
|
||||
{file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"},
|
||||
{file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"},
|
||||
{file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"},
|
||||
{file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"},
|
||||
{file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"},
|
||||
{file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"},
|
||||
{file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"},
|
||||
{file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"},
|
||||
{file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"},
|
||||
{file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"},
|
||||
{file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"},
|
||||
{file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"},
|
||||
{file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
|
||||
{file = "typed_ast-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ad3b48cf2b487be140072fb86feff36801487d4abb7382bb1929aaac80638ea"},
|
||||
{file = "typed_ast-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:542cd732351ba8235f20faa0fc7398946fe1a57f2cdb289e5497e1e7f48cfedb"},
|
||||
{file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc2c11ae59003d4a26dda637222d9ae924387f96acae9492df663843aefad55"},
|
||||
{file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd5df1313915dbd70eaaa88c19030b441742e8b05e6103c631c83b75e0435ccc"},
|
||||
{file = "typed_ast-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:e34f9b9e61333ecb0f7d79c21c28aa5cd63bec15cb7e1310d7d3da6ce886bc9b"},
|
||||
{file = "typed_ast-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f818c5b81966d4728fec14caa338e30a70dfc3da577984d38f97816c4b3071ec"},
|
||||
{file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3042bfc9ca118712c9809201f55355479cfcdc17449f9f8db5e744e9625c6805"},
|
||||
{file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fff9fdcce59dc61ec1b317bdb319f8f4e6b69ebbe61193ae0a60c5f9333dc49"},
|
||||
{file = "typed_ast-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8e0b8528838ffd426fea8d18bde4c73bcb4167218998cc8b9ee0a0f2bfe678a6"},
|
||||
{file = "typed_ast-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ef1d96ad05a291f5c36895d86d1375c0ee70595b90f6bb5f5fdbee749b146db"},
|
||||
{file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed44e81517364cb5ba367e4f68fca01fba42a7a4690d40c07886586ac267d9b9"},
|
||||
{file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f60d9de0d087454c91b3999a296d0c4558c1666771e3460621875021bf899af9"},
|
||||
{file = "typed_ast-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9e237e74fd321a55c90eee9bc5d44be976979ad38a29bbd734148295c1ce7617"},
|
||||
{file = "typed_ast-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee852185964744987609b40aee1d2eb81502ae63ee8eef614558f96a56c1902d"},
|
||||
{file = "typed_ast-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27e46cdd01d6c3a0dd8f728b6a938a6751f7bd324817501c15fb056307f918c6"},
|
||||
{file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d64dabc6336ddc10373922a146fa2256043b3b43e61f28961caec2a5207c56d5"},
|
||||
{file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8cdf91b0c466a6c43f36c1964772918a2c04cfa83df8001ff32a89e357f8eb06"},
|
||||
{file = "typed_ast-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:9cc9e1457e1feb06b075c8ef8aeb046a28ec351b1958b42c7c31c989c841403a"},
|
||||
{file = "typed_ast-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e20d196815eeffb3d76b75223e8ffed124e65ee62097e4e73afb5fec6b993e7a"},
|
||||
{file = "typed_ast-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:37e5349d1d5de2f4763d534ccb26809d1c24b180a477659a12c4bde9dd677d74"},
|
||||
{file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f1a27592fac87daa4e3f16538713d705599b0a27dfe25518b80b6b017f0a6d"},
|
||||
{file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8831479695eadc8b5ffed06fdfb3e424adc37962a75925668deeb503f446c0a3"},
|
||||
{file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"},
|
||||
{file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||
@@ -1401,33 +1401,34 @@ typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
|
||||
]
|
||||
virtualenv = [
|
||||
{file = "virtualenv-20.13.4-py2.py3-none-any.whl", hash = "sha256:c3e01300fb8495bc00ed70741f5271fc95fed067eb7106297be73d30879af60c"},
|
||||
{file = "virtualenv-20.13.4.tar.gz", hash = "sha256:ce8901d3bbf3b90393498187f2d56797a8a452fb2d0d7efc6fd837554d6f679c"},
|
||||
{file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"},
|
||||
{file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"},
|
||||
]
|
||||
watchdog = [
|
||||
{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"},
|
||||
{file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:177bae28ca723bc00846466016d34f8c1d6a621383b6caca86745918d55c7383"},
|
||||
{file = "watchdog-2.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d1cf7dfd747dec519486a98ef16097e6c480934ef115b16f18adb341df747a4"},
|
||||
{file = "watchdog-2.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f14ce6adea2af1bba495acdde0e510aecaeb13b33f7bd2f6324e551b26688ca"},
|
||||
{file = "watchdog-2.1.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4d0e98ac2e8dd803a56f4e10438b33a2d40390a72750cff4939b4b274e7906fa"},
|
||||
{file = "watchdog-2.1.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:81982c7884aac75017a6ecc72f1a4fedbae04181a8665a34afce9539fc1b3fab"},
|
||||
{file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0b4a1fe6201c6e5a1926f5767b8664b45f0fcb429b62564a41f490ff1ce1dc7a"},
|
||||
{file = "watchdog-2.1.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e6ae29b72977f2e1ee3d0b760d7ee47896cb53e831cbeede3e64485e5633cc8"},
|
||||
{file = "watchdog-2.1.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b9777664848160449e5b4260e0b7bc1ae0f6f4992a8b285db4ec1ef119ffa0e2"},
|
||||
{file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:19b36d436578eb437e029c6b838e732ed08054956366f6dd11875434a62d2b99"},
|
||||
{file = "watchdog-2.1.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b61acffaf5cd5d664af555c0850f9747cc5f2baf71e54bbac164c58398d6ca7b"},
|
||||
{file = "watchdog-2.1.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e877c70245424b06c41ac258023ea4bd0c8e4ff15d7c1368f17cd0ae6e351dd"},
|
||||
{file = "watchdog-2.1.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d802d65262a560278cf1a65ef7cae4e2bc7ecfe19e5451349e4c67e23c9dc420"},
|
||||
{file = "watchdog-2.1.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b3750ee5399e6e9c69eae8b125092b871ee9e2fcbd657a92747aea28f9056a5c"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:ed6d9aad09a2a948572224663ab00f8975fae242aa540509737bb4507133fa2d"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_armv7l.whl", hash = "sha256:b26e13e8008dcaea6a909e91d39b629a39635d1a8a7239dd35327c74f4388601"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_i686.whl", hash = "sha256:0908bb50f6f7de54d5d31ec3da1654cb7287c6b87bce371954561e6de379d690"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64.whl", hash = "sha256:bdcbf75580bf4b960fb659bbccd00123d83119619195f42d721e002c1621602f"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:81a5861d0158a7e55fe149335fb2bbfa6f48cbcbd149b52dbe2cd9a544034bbd"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_s390x.whl", hash = "sha256:03b43d583df0f18782a0431b6e9e9965c5b3f7cf8ec36a00b930def67942c385"},
|
||||
{file = "watchdog-2.1.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ae934e34c11aa8296c18f70bf66ed60e9870fcdb4cc19129a04ca83ab23e7055"},
|
||||
{file = "watchdog-2.1.7-py3-none-win32.whl", hash = "sha256:49639865e3db4be032a96695c98ac09eed39bbb43fe876bb217da8f8101689a6"},
|
||||
{file = "watchdog-2.1.7-py3-none-win_amd64.whl", hash = "sha256:340b875aecf4b0e6672076a6f05cfce6686935559bb6d34cebedee04126a9566"},
|
||||
{file = "watchdog-2.1.7-py3-none-win_ia64.whl", hash = "sha256:351e09b6d9374d5bcb947e6ac47a608ec25b9d70583e9db00b2fcdb97b00b572"},
|
||||
{file = "watchdog-2.1.7.tar.gz", hash = "sha256:3fd47815353be9c44eebc94cc28fe26b2b0c5bd889dafc4a5a7cbdf924143480"},
|
||||
]
|
||||
yarl = [
|
||||
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},
|
||||
@@ -1504,6 +1505,6 @@ yarl = [
|
||||
{file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
|
||||
{file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
|
||||
{file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
|
||||
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
|
||||
]
|
||||
|
||||
@@ -17,16 +17,21 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
textual = "textual.cli.cli:run"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
rich = "^12.0.0"
|
||||
|
||||
#rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"}
|
||||
typing-extensions = { version = "^3.10.0", python = "<3.8" }
|
||||
click = "8.1.2"
|
||||
importlib-metadata = "^4.11.3"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^6.2.3"
|
||||
black = "^22.1.0"
|
||||
black = "^22.3.0"
|
||||
mypy = "^0.931"
|
||||
pytest-cov = "^2.12.1"
|
||||
mkdocs = "^1.2.3"
|
||||
|
||||
46
sandbox/align.css
Normal file
46
sandbox/align.css
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
|
||||
Screen {
|
||||
layout: vertical;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
Widget {
|
||||
margin:1;
|
||||
}
|
||||
|
||||
#thing {
|
||||
|
||||
width: auto;
|
||||
height: 10;
|
||||
background:magenta;
|
||||
margin: 3;
|
||||
padding: 1;
|
||||
border: solid white;
|
||||
box-sizing: border-box;
|
||||
border: solid white;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
|
||||
#thing2 {
|
||||
border: solid white;
|
||||
/* outline: heavy blue; */
|
||||
height: 10;
|
||||
padding: 1 2;
|
||||
box-sizing: border-box;
|
||||
|
||||
max-height: 100vh;
|
||||
|
||||
background:green;
|
||||
align-horizontal: center;
|
||||
color:white;
|
||||
}
|
||||
|
||||
|
||||
#thing3 {
|
||||
height: 10;
|
||||
margin: 1;
|
||||
background:blue;
|
||||
align-horizontal: center;
|
||||
}
|
||||
25
sandbox/align.py
Normal file
25
sandbox/align.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from rich.text import Text
|
||||
|
||||
from textual.app import App
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
class Thing(Widget):
|
||||
def render(self):
|
||||
return Text.from_markup("Hello, World. [b magenta]Lorem impsum.")
|
||||
|
||||
|
||||
class AlignApp(App):
|
||||
def on_load(self):
|
||||
self.bind("t", "log_tree")
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.log("MOUNTED")
|
||||
self.mount(thing=Thing(), thing2=Static("0123456789"), thing3=Widget())
|
||||
|
||||
def action_log_tree(self):
|
||||
self.log(self.screen.tree)
|
||||
|
||||
|
||||
AlignApp.run(css_file="align.css", log="textual.log", watch_css=True)
|
||||
@@ -17,10 +17,11 @@
|
||||
App > Screen {
|
||||
layout: dock;
|
||||
docks: side=left/1;
|
||||
background: $background;
|
||||
color: $text-background;
|
||||
background: $surface;
|
||||
color: $text-surface;
|
||||
}
|
||||
|
||||
|
||||
#sidebar {
|
||||
color: $text-primary;
|
||||
background: $primary;
|
||||
@@ -67,13 +68,13 @@ App > Screen {
|
||||
background: $background;
|
||||
layout: vertical;
|
||||
overflow-y: scroll;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Tweet {
|
||||
height: 22;
|
||||
max-width: 80;
|
||||
height: 12;
|
||||
width: 80;
|
||||
|
||||
margin: 1 3;
|
||||
background: $panel;
|
||||
color: $text-panel;
|
||||
@@ -81,7 +82,24 @@ Tweet {
|
||||
/* border: outer $primary; */
|
||||
padding: 1;
|
||||
border: wide $panel-darken-2;
|
||||
overflow-y: scroll
|
||||
overflow-y: scroll;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
width: 80;
|
||||
overflow-y: scroll;
|
||||
max-width:80;
|
||||
height: 20;
|
||||
align-horizontal: center;
|
||||
layout: vertical;
|
||||
}
|
||||
|
||||
.code {
|
||||
|
||||
height: 34;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +110,7 @@ TweetHeader {
|
||||
}
|
||||
|
||||
TweetBody {
|
||||
width: 100%;
|
||||
background: $panel;
|
||||
color: $text-panel;
|
||||
height:20;
|
||||
@@ -159,7 +178,7 @@ OptionItem:hover {
|
||||
}
|
||||
|
||||
Error {
|
||||
max-width: 80;
|
||||
width: 80;
|
||||
height:3;
|
||||
background: $error;
|
||||
color: $text-error;
|
||||
@@ -168,10 +187,11 @@ Error {
|
||||
margin: 1 3;
|
||||
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
Warning {
|
||||
max-width: 80;
|
||||
width: 80;
|
||||
height:3;
|
||||
background: $warning;
|
||||
color: $text-warning-fade-1;
|
||||
@@ -179,15 +199,23 @@ Warning {
|
||||
border-bottom: hkey $warning-darken-2;
|
||||
margin: 1 2;
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
Success {
|
||||
max-width: 80;
|
||||
width: 80;
|
||||
height:3;
|
||||
box-sizing: border-box;
|
||||
background: $success-lighten-3;
|
||||
color: $text-success-lighten-3-fade-1;
|
||||
border-top: hkey $success;
|
||||
border-bottom: hkey $success;
|
||||
margin: 1 2;
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
|
||||
.horizontal {
|
||||
layout: horizontal
|
||||
}
|
||||
@@ -1,9 +1,47 @@
|
||||
from rich.align import Align
|
||||
from rich.console import RenderableType
|
||||
from rich.syntax import Syntax
|
||||
from rich.text import Text
|
||||
|
||||
from textual.app import App
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Static
|
||||
|
||||
CODE = '''
|
||||
class Offset(NamedTuple):
|
||||
"""A point defined by x and y coordinates."""
|
||||
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
|
||||
@property
|
||||
def is_origin(self) -> bool:
|
||||
"""Check if the point is at the origin (0, 0)"""
|
||||
return self == (0, 0)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return self != (0, 0)
|
||||
|
||||
def __add__(self, other: object) -> Offset:
|
||||
if isinstance(other, tuple):
|
||||
_x, _y = self
|
||||
x, y = other
|
||||
return Offset(_x + x, _y + y)
|
||||
return NotImplemented
|
||||
|
||||
def __sub__(self, other: object) -> Offset:
|
||||
if isinstance(other, tuple):
|
||||
_x, _y = self
|
||||
x, y = other
|
||||
return Offset(_x - x, _y - y)
|
||||
return NotImplemented
|
||||
|
||||
def __mul__(self, other: object) -> Offset:
|
||||
if isinstance(other, (float, int)):
|
||||
x, y = self
|
||||
return Offset(int(x * other), int(y * other))
|
||||
return NotImplemented
|
||||
'''
|
||||
|
||||
|
||||
lorem = Text.from_markup(
|
||||
@@ -56,9 +94,25 @@ class BasicApp(App):
|
||||
def on_mount(self):
|
||||
"""Build layout here."""
|
||||
self.mount(
|
||||
header=Widget(),
|
||||
header=Static(
|
||||
Align.center(
|
||||
"[b]This is a [u]Textual[/u] app, running in the terminal",
|
||||
vertical="middle",
|
||||
)
|
||||
),
|
||||
content=Widget(
|
||||
Tweet(TweetBody(), Widget(classes={"button"})),
|
||||
Tweet(
|
||||
TweetBody(),
|
||||
# Widget(
|
||||
# Widget(classes={"button"}),
|
||||
# Widget(classes={"button"}),
|
||||
# classes={"horizontal"},
|
||||
# ),
|
||||
),
|
||||
Widget(
|
||||
Static(Syntax(CODE, "python"), classes={"code"}),
|
||||
classes={"scrollable"},
|
||||
),
|
||||
Error(),
|
||||
Tweet(TweetBody()),
|
||||
Warning(),
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
.list-item {
|
||||
height: 8;
|
||||
|
||||
min-width: 80;
|
||||
background: dark_blu;
|
||||
padding: 2x;
|
||||
|
||||
@@ -9,7 +9,7 @@ import warnings
|
||||
from asyncio import AbstractEventLoop
|
||||
from contextlib import redirect_stdout
|
||||
from time import perf_counter
|
||||
from typing import Any, Iterable, Type, TYPE_CHECKING
|
||||
from typing import Any, Iterable, TextIO, Type, TYPE_CHECKING
|
||||
|
||||
import rich
|
||||
import rich.repr
|
||||
@@ -64,6 +64,7 @@ DEFAULT_COLORS = ColorSystem(
|
||||
success="#6d9f71",
|
||||
accent="#ffa62b",
|
||||
system="#5a4599",
|
||||
dark_surface="#292929",
|
||||
)
|
||||
|
||||
|
||||
@@ -100,7 +101,9 @@ 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(markup=False, highlight=False, emoji=False)
|
||||
self.console = Console(
|
||||
file=sys.__stdout__, markup=False, highlight=False, emoji=False
|
||||
)
|
||||
self.error_console = Console(markup=False, stderr=True)
|
||||
self._screen = screen
|
||||
self.driver_class = driver_class or self.get_driver_class()
|
||||
@@ -122,6 +125,7 @@ class App(DOMNode):
|
||||
self._title = title
|
||||
|
||||
self._log_console: Console | None = None
|
||||
self._log_file: TextIO | None = None
|
||||
if log:
|
||||
self._log_file = open(log, "wt")
|
||||
self._log_console = Console(
|
||||
@@ -131,9 +135,6 @@ class App(DOMNode):
|
||||
highlight=False,
|
||||
width=100,
|
||||
)
|
||||
else:
|
||||
self._log_console = None
|
||||
self._log_file = None
|
||||
|
||||
self.log_verbosity = log_verbosity
|
||||
|
||||
@@ -459,6 +460,7 @@ class App(DOMNode):
|
||||
Args:
|
||||
error (Exception): An exception instance.
|
||||
"""
|
||||
|
||||
if hasattr(error, "__rich__"):
|
||||
# Exception has a rich method, so we can defer to that for the rendering
|
||||
self.panic(error)
|
||||
@@ -489,15 +491,9 @@ class App(DOMNode):
|
||||
if os.getenv("TEXTUAL_DEVTOOLS") == "1":
|
||||
try:
|
||||
await self.devtools.connect()
|
||||
if self._log_console:
|
||||
self._log_console.print(
|
||||
f"Connected to devtools ({self.devtools.url})"
|
||||
)
|
||||
self.log(f"Connected to devtools ({self.devtools.url})")
|
||||
except DevtoolsConnectionError:
|
||||
if self._log_console:
|
||||
self._log_console.print(
|
||||
f"Couldn't connect to devtools ({self.devtools.url})"
|
||||
)
|
||||
self.log(f"Couldn't connect to devtools ({self.devtools.url})")
|
||||
try:
|
||||
if self.css_file is not None:
|
||||
self.stylesheet.read(self.css_file)
|
||||
@@ -532,10 +528,7 @@ class App(DOMNode):
|
||||
|
||||
with redirect_stdout(StdoutRedirector(self.devtools, self._log_file)): # type: ignore
|
||||
await super().process_messages()
|
||||
log("Message processing stopped")
|
||||
with timer("animator.stop()"):
|
||||
await self.animator.stop()
|
||||
with timer("self.close_all()"):
|
||||
await self.close_all()
|
||||
finally:
|
||||
driver.stop_application_mode()
|
||||
|
||||
88
src/textual/box_model.py
Normal file
88
src/textual/box_model.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
from operator import is_
|
||||
|
||||
|
||||
from typing import Callable, NamedTuple, TYPE_CHECKING
|
||||
|
||||
from .geometry import Size, Spacing
|
||||
from .css.styles import StylesBase
|
||||
|
||||
|
||||
class BoxModel(NamedTuple):
|
||||
"""The result of `get_box_model`."""
|
||||
|
||||
size: Size # Content + padding + border
|
||||
margin: Spacing # Additional margin
|
||||
|
||||
|
||||
def get_box_model(
|
||||
styles: StylesBase,
|
||||
container: Size,
|
||||
viewport: Size,
|
||||
get_content_width: Callable[[Size, Size], int],
|
||||
get_content_height: Callable[[Size, Size], int],
|
||||
) -> BoxModel:
|
||||
"""Resolve the box model for this Styles.
|
||||
|
||||
Args:
|
||||
styles (StylesBase): Styles object.
|
||||
container (Size): The size of the widget container.
|
||||
viewport (Size): The viewport size.
|
||||
get_auto_width (Callable): A callable which accepts container size and parent size and returns a width.
|
||||
get_auto_height (Callable): A callable which accepts container size and parent size and returns a height.
|
||||
|
||||
Returns:
|
||||
BoxModel: A tuple with the size of the content area and margin.
|
||||
"""
|
||||
|
||||
has_rule = styles.has_rule
|
||||
width, height = container
|
||||
is_content_box = styles.box_sizing == "content-box"
|
||||
gutter = styles.padding + styles.border.spacing
|
||||
|
||||
if not has_rule("width"):
|
||||
width = container.width
|
||||
elif styles.width.is_auto:
|
||||
# When width is auto, we want enough space to always fit the content
|
||||
width = get_content_width(container, viewport)
|
||||
if not is_content_box:
|
||||
# If box sizing is border box we want to enlarge the width so that it
|
||||
# can accommodate padding + border
|
||||
width += gutter.width
|
||||
else:
|
||||
width = styles.width.resolve_dimension(container, viewport)
|
||||
|
||||
if not has_rule("height"):
|
||||
height = container.height
|
||||
elif styles.height.is_auto:
|
||||
height = get_content_height(container, viewport)
|
||||
if not is_content_box:
|
||||
height += gutter.height
|
||||
else:
|
||||
height = styles.height.resolve_dimension(container, viewport)
|
||||
|
||||
if is_content_box:
|
||||
gutter_width, gutter_height = gutter.totals
|
||||
width += gutter_width
|
||||
height += gutter_height
|
||||
|
||||
if has_rule("min_width"):
|
||||
min_width = styles.min_width.resolve_dimension(container, viewport)
|
||||
width = max(width, min_width)
|
||||
|
||||
if has_rule("max_width"):
|
||||
max_width = styles.max_width.resolve_dimension(container, viewport)
|
||||
width = min(width, max_width)
|
||||
|
||||
if has_rule("min_height"):
|
||||
min_height = styles.min_height.resolve_dimension(container, viewport)
|
||||
height = max(height, min_height)
|
||||
|
||||
if has_rule("max_height"):
|
||||
max_height = styles.max_height.resolve_dimension(container, viewport)
|
||||
height = min(height, max_height)
|
||||
|
||||
size = Size(width, height)
|
||||
margin = styles.margin
|
||||
|
||||
return BoxModel(size, margin)
|
||||
0
src/textual/cli/__init__.py
Normal file
0
src/textual/cli/__init__.py
Normal file
15
src/textual/cli/cli.py
Normal file
15
src/textual/cli/cli.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import click
|
||||
from importlib_metadata import version
|
||||
|
||||
from textual.devtools.server import _run_devtools
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version("textual"))
|
||||
def run():
|
||||
pass
|
||||
|
||||
|
||||
@run.command(help="Run the Textual Devtools console")
|
||||
def console():
|
||||
_run_devtools()
|
||||
@@ -139,6 +139,7 @@ class Color(NamedTuple):
|
||||
@property
|
||||
def rich_color(self) -> RichColor:
|
||||
"""This color encoded in Rich's Color class."""
|
||||
# TODO: This isn't cheap as I'd like - cache in a LRUCache ?
|
||||
r, g, b, _a = self
|
||||
return RichColor.from_rgb(r, g, b)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ from typing import Iterable, NamedTuple, TYPE_CHECKING, cast
|
||||
import rich.repr
|
||||
from rich.style import Style
|
||||
|
||||
from ._help_text import scalar_help_text
|
||||
from .. import log
|
||||
from ._help_text import (
|
||||
spacing_wrong_number_of_values,
|
||||
scalar_help_text,
|
||||
@@ -50,10 +52,14 @@ 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
|
||||
self,
|
||||
units: set[Unit] | None = None,
|
||||
percent_unit: Unit = Unit.WIDTH,
|
||||
allow_auto: bool = True,
|
||||
) -> None:
|
||||
self.units: set[Unit] = units or {*UNIT_SYMBOL}
|
||||
self.percent_unit = percent_unit
|
||||
self.allow_auto = allow_auto
|
||||
super().__init__()
|
||||
|
||||
def __set_name__(self, owner: Styles, name: str) -> None:
|
||||
@@ -96,7 +102,7 @@ class ScalarProperty:
|
||||
obj.clear_rule(self.name)
|
||||
obj.refresh(layout=True)
|
||||
return
|
||||
if isinstance(value, float) or isinstance(value, int):
|
||||
if isinstance(value, (int, float)):
|
||||
new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
|
||||
elif isinstance(value, Scalar):
|
||||
new_value = value
|
||||
@@ -112,12 +118,23 @@ class ScalarProperty:
|
||||
)
|
||||
else:
|
||||
raise StyleValueError("expected float, int, Scalar, or None")
|
||||
|
||||
if (
|
||||
new_value is not None
|
||||
and new_value.unit == Unit.AUTO
|
||||
and not self.allow_auto
|
||||
):
|
||||
raise StyleValueError("'auto' not allowed here")
|
||||
|
||||
if new_value.unit != Unit.AUTO:
|
||||
if new_value is not None and new_value.unit not in self.units:
|
||||
raise StyleValueError(
|
||||
f"{self.name} units must be one of {friendly_list(get_symbols(self.units))}"
|
||||
)
|
||||
if new_value is not None and new_value.is_percent:
|
||||
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
|
||||
new_value = Scalar(
|
||||
float(new_value.value), self.percent_unit, Unit.WIDTH
|
||||
)
|
||||
if obj.set_rule(self.name, new_value):
|
||||
obj.refresh(layout=True)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ from ._help_text import (
|
||||
color_property_help_text,
|
||||
)
|
||||
from .constants import (
|
||||
VALID_ALIGN_HORIZONTAL,
|
||||
VALID_ALIGN_VERTICAL,
|
||||
VALID_BORDER,
|
||||
VALID_BOX_SIZING,
|
||||
VALID_EDGE,
|
||||
@@ -654,3 +656,32 @@ class StylesBuilder:
|
||||
transitions[css_property] = Transition(duration, easing, delay)
|
||||
|
||||
self.styles._rules["transitions"] = transitions
|
||||
|
||||
def process_align(self, name: str, tokens: list[Token]) -> None:
|
||||
if len(tokens) != 2:
|
||||
self.error(name, tokens[0], "expected two tokens")
|
||||
token_horizontal = tokens[0]
|
||||
token_vertical = tokens[1]
|
||||
if token_horizontal.name != "token":
|
||||
self.error(
|
||||
name,
|
||||
token_horizontal,
|
||||
f"invalid token {token_horizontal!r}, expected {friendly_list(VALID_ALIGN_HORIZONTAL)}",
|
||||
)
|
||||
if token_vertical.name != "token":
|
||||
self.error(
|
||||
name,
|
||||
token_vertical,
|
||||
f"invalid token {token_vertical!r}, expected {friendly_list(VALID_ALIGN_VERTICAL)}",
|
||||
)
|
||||
|
||||
self.styles._rules["align_horizontal"] = token_horizontal.value
|
||||
self.styles._rules["align_vertical"] = token_vertical.value
|
||||
|
||||
def process_align_horizontal(self, name: str, tokens: list[Token]) -> None:
|
||||
value = self._process_enum(name, tokens, VALID_ALIGN_HORIZONTAL)
|
||||
self.styles._rules["align_horizontal"] = value
|
||||
|
||||
def process_align_vertical(self, name: str, tokens: list[Token]) -> None:
|
||||
value = self._process_enum(name, tokens, VALID_ALIGN_VERTICAL)
|
||||
self.styles._rules["align_vertical"] = value
|
||||
|
||||
@@ -29,6 +29,7 @@ VALID_LAYOUT: Final = {"dock", "vertical", "grid"}
|
||||
|
||||
VALID_BOX_SIZING: Final = {"border-box", "content-box"}
|
||||
VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"}
|
||||
|
||||
VALID_ALIGN_HORIZONTAL: Final = {"left", "center", "right"}
|
||||
VALID_ALIGN_VERTICAL: Final = {"top", "middle", "bottom"}
|
||||
|
||||
NULL_SPACING: Final = Spacing.all(0)
|
||||
|
||||
@@ -3,11 +3,10 @@ from __future__ import annotations
|
||||
from enum import Enum, unique
|
||||
from functools import lru_cache
|
||||
import re
|
||||
from typing import Iterable, NamedTuple, TYPE_CHECKING
|
||||
from typing import Callable, Iterable, NamedTuple, TYPE_CHECKING
|
||||
|
||||
import rich.repr
|
||||
|
||||
from textual.css.tokenizer import Token
|
||||
|
||||
from .. import log
|
||||
from ..geometry import Offset
|
||||
@@ -34,6 +33,7 @@ class Unit(Enum):
|
||||
HEIGHT = 5
|
||||
VIEW_WIDTH = 6
|
||||
VIEW_HEIGHT = 7
|
||||
AUTO = 8
|
||||
|
||||
|
||||
UNIT_SYMBOL = {
|
||||
@@ -107,6 +107,10 @@ class Scalar(NamedTuple):
|
||||
def symbol(self) -> str:
|
||||
return UNIT_SYMBOL[self.unit]
|
||||
|
||||
@property
|
||||
def is_auto(self) -> bool:
|
||||
return self.unit == Unit.AUTO
|
||||
|
||||
@classmethod
|
||||
def from_number(cls, value: float) -> Scalar:
|
||||
return cls(float(value), Unit.CELLS, Unit.WIDTH)
|
||||
@@ -124,6 +128,9 @@ class Scalar(NamedTuple):
|
||||
Returns:
|
||||
Scalar: New scalar
|
||||
"""
|
||||
if token.lower() == "auto":
|
||||
scalar = cls(1.0, Unit.AUTO, Unit.AUTO)
|
||||
else:
|
||||
match = _MATCH_SCALAR(token)
|
||||
if match is None:
|
||||
raise ScalarParseError(f"{token!r} is not a valid scalar")
|
||||
@@ -142,12 +149,13 @@ class Scalar(NamedTuple):
|
||||
viewport (tuple[int, int]): Size of the viewport (typically terminal size)
|
||||
|
||||
Raises:
|
||||
ScalarResolveError: _description_
|
||||
ScalarResolveError: If the unit is unknown.
|
||||
|
||||
Returns:
|
||||
float: _description_
|
||||
int: A size (in cells)
|
||||
"""
|
||||
value, unit, percent_unit = self
|
||||
|
||||
if unit == Unit.PERCENT:
|
||||
unit = percent_unit
|
||||
try:
|
||||
|
||||
@@ -13,7 +13,7 @@ from rich.style import Style
|
||||
from .. import log
|
||||
from .._animator import Animation, EasingFunction
|
||||
from ..color import Color
|
||||
from ..geometry import Size, Spacing
|
||||
from ..geometry import Offset, Size, Spacing
|
||||
from ._style_properties import (
|
||||
BorderProperty,
|
||||
BoxProperty,
|
||||
@@ -32,17 +32,28 @@ from ._style_properties import (
|
||||
TransitionsProperty,
|
||||
FractionalProperty,
|
||||
)
|
||||
from .constants import VALID_BOX_SIZING, VALID_DISPLAY, VALID_VISIBILITY, VALID_OVERFLOW
|
||||
from .constants import (
|
||||
VALID_ALIGN_HORIZONTAL,
|
||||
VALID_ALIGN_VERTICAL,
|
||||
VALID_BOX_SIZING,
|
||||
VALID_DISPLAY,
|
||||
VALID_VISIBILITY,
|
||||
VALID_OVERFLOW,
|
||||
)
|
||||
from .scalar import Scalar, ScalarOffset, Unit
|
||||
from .scalar_animation import ScalarAnimation
|
||||
from .transition import Transition
|
||||
from .types import (
|
||||
BoxSizing,
|
||||
Display,
|
||||
AlignHorizontal,
|
||||
AlignVertical,
|
||||
Edge,
|
||||
AlignHorizontal,
|
||||
Overflow,
|
||||
Specificity3,
|
||||
Specificity4,
|
||||
AlignVertical,
|
||||
Visibility,
|
||||
)
|
||||
|
||||
@@ -115,8 +126,12 @@ class RulesMap(TypedDict, total=False):
|
||||
scrollbar_background_hover: Color
|
||||
scrollbar_background_active: Color
|
||||
|
||||
align_horizontal: AlignHorizontal
|
||||
align_vertical: AlignVertical
|
||||
|
||||
|
||||
RULE_NAMES = list(RulesMap.__annotations__.keys())
|
||||
RULE_NAMES_SET = frozenset(RULE_NAMES)
|
||||
_rule_getter = attrgetter(*RULE_NAMES)
|
||||
|
||||
|
||||
@@ -178,10 +193,10 @@ class StylesBase(ABC):
|
||||
box_sizing = StringEnumProperty(VALID_BOX_SIZING, "border-box", layout=True)
|
||||
width = ScalarProperty(percent_unit=Unit.WIDTH)
|
||||
height = ScalarProperty(percent_unit=Unit.HEIGHT)
|
||||
min_width = ScalarProperty(percent_unit=Unit.WIDTH)
|
||||
min_height = ScalarProperty(percent_unit=Unit.HEIGHT)
|
||||
max_width = ScalarProperty(percent_unit=Unit.WIDTH)
|
||||
max_height = ScalarProperty(percent_unit=Unit.HEIGHT)
|
||||
min_width = ScalarProperty(percent_unit=Unit.WIDTH, allow_auto=False)
|
||||
min_height = ScalarProperty(percent_unit=Unit.HEIGHT, allow_auto=False)
|
||||
max_width = ScalarProperty(percent_unit=Unit.WIDTH, allow_auto=False)
|
||||
max_height = ScalarProperty(percent_unit=Unit.HEIGHT, allow_auto=False)
|
||||
|
||||
dock = DockProperty()
|
||||
docks = DocksProperty()
|
||||
@@ -203,6 +218,9 @@ class StylesBase(ABC):
|
||||
scrollbar_background_hover = ColorProperty("#444444")
|
||||
scrollbar_background_active = ColorProperty("black")
|
||||
|
||||
align_horizontal = StringEnumProperty(VALID_ALIGN_HORIZONTAL, "left")
|
||||
align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top")
|
||||
|
||||
def __eq__(self, styles: object) -> bool:
|
||||
"""Check that Styles containts the same rules."""
|
||||
if not isinstance(styles, StylesBase):
|
||||
@@ -344,70 +362,43 @@ class StylesBase(ABC):
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_box_model(
|
||||
self, container_size: Size, parent_size: Size
|
||||
) -> tuple[Size, Spacing]:
|
||||
"""Resolve the box model for this Styles.
|
||||
def align_width(self, width: int, parent_width: int) -> int:
|
||||
"""Align the width dimension.
|
||||
|
||||
Args:
|
||||
container_size (Size): The size of the widget container.
|
||||
parent_size (Size): The size widget's parent.
|
||||
width (int): Width of the content.
|
||||
parent_width (int): Width of the parent container.
|
||||
|
||||
Returns:
|
||||
tuple[Size, Spacing]: A tuple with the size of the content area and margin.
|
||||
int: An offset to add to the X coordinate.
|
||||
"""
|
||||
has_rule = self.has_rule
|
||||
width, height = container_size
|
||||
|
||||
if has_rule("width"):
|
||||
width = self.width.resolve_dimension(container_size, parent_size)
|
||||
offset_x = 0
|
||||
align_horizontal = self.align_horizontal
|
||||
if align_horizontal != "left":
|
||||
if align_horizontal == "center":
|
||||
offset_x = (parent_width - width) // 2
|
||||
else:
|
||||
width = max(0, width - self.margin.width)
|
||||
offset_x = parent_width - width
|
||||
return offset_x
|
||||
|
||||
if self.min_width:
|
||||
min_width = self.min_width.resolve_dimension(container_size, parent_size)
|
||||
width = max(width, min_width)
|
||||
def align_height(self, height: int, parent_height: int) -> int:
|
||||
"""Align the height dimensions
|
||||
|
||||
if self.max_width:
|
||||
max_width = self.max_width.resolve_dimension(container_size, parent_size)
|
||||
width = min(width, max_width)
|
||||
Args:
|
||||
height (int): Height of the content.
|
||||
parent_height (int): Height of the parent container.
|
||||
|
||||
if has_rule("height"):
|
||||
height = self.height.resolve_dimension(container_size, parent_size)
|
||||
Returns:
|
||||
int: An offset to add to the Y coordinate.
|
||||
"""
|
||||
offset_y = 0
|
||||
align_vertical = self.align_vertical
|
||||
if align_vertical != "top":
|
||||
if align_vertical == "middle":
|
||||
offset_y = (parent_height - height) // 2
|
||||
else:
|
||||
height = max(0, height - self.margin.height)
|
||||
|
||||
if self.min_height:
|
||||
min_height = self.min_height.resolve_dimension(container_size, parent_size)
|
||||
height = max(height, min_height)
|
||||
|
||||
if self.max_height:
|
||||
max_height = self.max_height.resolve_dimension(container_size, parent_size)
|
||||
height = min(width, max_height)
|
||||
|
||||
# TODO: box sizing
|
||||
|
||||
size = Size(width, height)
|
||||
margin = Spacing(0, 0, 0, 0)
|
||||
|
||||
if self.box_sizing == "content-box":
|
||||
|
||||
if has_rule("padding"):
|
||||
size += self.padding.totals
|
||||
if has_rule("border"):
|
||||
size += self.border.spacing.totals
|
||||
if has_rule("margin"):
|
||||
margin = self.margin
|
||||
|
||||
else: # border-box
|
||||
if has_rule("padding"):
|
||||
size -= self.padding.totals
|
||||
if has_rule("border"):
|
||||
size -= self.border.spacing.totals
|
||||
if has_rule("margin"):
|
||||
margin = self.margin
|
||||
|
||||
return size, margin
|
||||
offset_y = parent_height - height
|
||||
return offset_y
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
@@ -425,6 +416,7 @@ class Styles(StylesBase):
|
||||
return Styles(node=self.node, _rules=self.get_rules(), important=self.important)
|
||||
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
assert rule in RULE_NAMES_SET, f"no such rule {rule!r}"
|
||||
return rule in self._rules
|
||||
|
||||
def clear_rule(self, rule: str) -> bool:
|
||||
@@ -675,6 +667,15 @@ class Styles(StylesBase):
|
||||
),
|
||||
)
|
||||
|
||||
if has_rule("align_horizontal") and has_rule("align_vertical"):
|
||||
append_declaration(
|
||||
"align", f"{self.align_horizontal} {self.align_vertical}"
|
||||
)
|
||||
elif has_rule("align_horizontal"):
|
||||
append_declaration("align-horizontal", self.align_horizontal)
|
||||
elif has_rule("align_horizontal"):
|
||||
append_declaration("align-vertical", self.align_vertical)
|
||||
|
||||
lines.sort()
|
||||
return lines
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ EdgeType = Literal[
|
||||
]
|
||||
Visibility = Literal["visible", "hidden", "initial", "inherit"]
|
||||
Display = Literal["block", "none"]
|
||||
AlignHorizontal = Literal["left", "center", "right"]
|
||||
AlignVertical = Literal["top", "middle", "bottom"]
|
||||
BoxSizing = Literal["border-box", "content-box"]
|
||||
Overflow = Literal["scroll", "hidden", "auto"]
|
||||
EdgeStyle = Tuple[str, Color]
|
||||
|
||||
@@ -16,7 +16,7 @@ from rich.console import Console
|
||||
from rich.segment import Segment
|
||||
|
||||
|
||||
DEFAULT_PORT = 8081
|
||||
DEVTOOLS_PORT = 8081
|
||||
WEBSOCKET_CONNECT_TIMEOUT = 3
|
||||
LOG_QUEUE_MAXSIZE = 512
|
||||
|
||||
@@ -90,7 +90,7 @@ class DevtoolsClient:
|
||||
port (int): The port the devtools server is accessed via, defaults to 8081
|
||||
"""
|
||||
|
||||
def __init__(self, host: str = "127.0.0.1", port: int = DEFAULT_PORT) -> None:
|
||||
def __init__(self, host: str = "127.0.0.1", port: int = DEVTOOLS_PORT) -> None:
|
||||
self.url: str = f"ws://{host}:{port}"
|
||||
self.session: aiohttp.ClientSession | None = None
|
||||
self.log_queue_task: Task | None = None
|
||||
|
||||
@@ -5,6 +5,8 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
from importlib_metadata import version
|
||||
from rich.containers import Renderables
|
||||
from rich.style import Style
|
||||
from rich.text import Text
|
||||
|
||||
@@ -21,10 +23,31 @@ from rich.rule import Rule
|
||||
from rich.segment import Segment, Segments
|
||||
from rich.table import Table
|
||||
|
||||
DevtoolsMessageLevel = Literal["info", "warning", "error"]
|
||||
DevConsoleMessageLevel = Literal["info", "warning", "error"]
|
||||
|
||||
|
||||
class DevtoolsLogMessage:
|
||||
class DevConsoleHeader:
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
lines = Renderables(
|
||||
[
|
||||
f"[bold]Textual Development Console [magenta]v{version('textual')}",
|
||||
"[magenta]Run a Textual app with the environment variable [b]TEXTUAL_DEVTOOLS=1[/] to connect.",
|
||||
"[magenta]Press [b]Ctrl+C[/] to quit.",
|
||||
]
|
||||
)
|
||||
render_options = options.update(width=options.max_width - 4)
|
||||
lines = console.render_lines(lines, render_options)
|
||||
new_line = Segment("\n")
|
||||
padding = Segment("▌", Style.parse("bright_magenta"))
|
||||
for line in lines:
|
||||
yield padding
|
||||
yield from line
|
||||
yield new_line
|
||||
|
||||
|
||||
class DevConsoleLog:
|
||||
"""Renderable representing a single log message
|
||||
|
||||
Args:
|
||||
@@ -61,18 +84,17 @@ class DevtoolsLogMessage:
|
||||
file_link = escape(f"file://{Path(self.path).absolute()}")
|
||||
file_and_line = escape(f"{Path(self.path).name}:{self.line_number}")
|
||||
table.add_row(
|
||||
f" [#888177]{local_time.time()} [dim]{timezone_name}[/]",
|
||||
f"[dim]{local_time.time()} {timezone_name}",
|
||||
Align.right(
|
||||
Text(f"{file_and_line} ", style=Style(color="#888177", link=file_link))
|
||||
Text(f"{file_and_line}", style=Style(dim=True, link=file_link))
|
||||
),
|
||||
style="on #292724",
|
||||
)
|
||||
yield table
|
||||
yield Segments(self.segments)
|
||||
|
||||
|
||||
class DevtoolsInternalMessage:
|
||||
"""Renderable for messages written by the devtools server itself
|
||||
class DevConsoleNotice:
|
||||
"""Renderable for messages written by the devtools console itself
|
||||
|
||||
Args:
|
||||
message (str): The message to display
|
||||
@@ -80,7 +102,7 @@ class DevtoolsInternalMessage:
|
||||
Determines colors used to render the message and the perceived importance.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, *, level: DevtoolsMessageLevel = "info") -> None:
|
||||
def __init__(self, message: str, *, level: DevConsoleMessageLevel = "info") -> None:
|
||||
self.message = message
|
||||
self.level = level
|
||||
|
||||
@@ -89,7 +111,7 @@ class DevtoolsInternalMessage:
|
||||
) -> RenderResult:
|
||||
level_to_style = {
|
||||
"info": "dim",
|
||||
"warning": "#FFA000",
|
||||
"error": "#C52828",
|
||||
"warning": "yellow",
|
||||
"error": "red",
|
||||
}
|
||||
yield Rule(self.message, style=level_to_style.get(self.level, "dim"))
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from aiohttp.web import run_app
|
||||
from aiohttp.web_app import Application
|
||||
from aiohttp.web_request import Request
|
||||
from aiohttp.web_routedef import get
|
||||
from aiohttp.web_ws import WebSocketResponse
|
||||
|
||||
from textual.devtools.client import DEFAULT_PORT
|
||||
from textual.devtools.client import DEVTOOLS_PORT
|
||||
from textual.devtools.service import DevtoolsService
|
||||
|
||||
DEFAULT_SIZE_CHANGE_POLL_DELAY_SECONDS = 2
|
||||
@@ -38,9 +36,13 @@ async def _on_startup(app: Application) -> None:
|
||||
await service.start()
|
||||
|
||||
|
||||
def _run_devtools(port: int) -> None:
|
||||
def _run_devtools() -> None:
|
||||
app = _make_devtools_aiohttp_app()
|
||||
run_app(app, port=port)
|
||||
|
||||
def noop_print(_: str):
|
||||
return None
|
||||
|
||||
run_app(app, port=DEVTOOLS_PORT, print=noop_print)
|
||||
|
||||
|
||||
def _make_devtools_aiohttp_app(
|
||||
@@ -65,8 +67,4 @@ def _make_devtools_aiohttp_app(
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
port = int(sys.argv[1])
|
||||
else:
|
||||
port = DEFAULT_PORT
|
||||
_run_devtools(port)
|
||||
_run_devtools()
|
||||
|
||||
@@ -14,7 +14,11 @@ from aiohttp.web_ws import WebSocketResponse
|
||||
from rich.console import Console
|
||||
from rich.markup import escape
|
||||
|
||||
from textual.devtools.renderables import DevtoolsLogMessage, DevtoolsInternalMessage
|
||||
from textual.devtools.renderables import (
|
||||
DevConsoleLog,
|
||||
DevConsoleNotice,
|
||||
DevConsoleHeader,
|
||||
)
|
||||
|
||||
QUEUEABLE_TYPES = {"client_log", "client_spillover"}
|
||||
|
||||
@@ -38,6 +42,7 @@ class DevtoolsService:
|
||||
async def start(self):
|
||||
"""Starts devtools tasks"""
|
||||
self.size_poll_task = asyncio.create_task(self._console_size_poller())
|
||||
self.console.print(DevConsoleHeader())
|
||||
|
||||
@property
|
||||
def clients_connected(self) -> bool:
|
||||
@@ -167,7 +172,7 @@ class ClientHandler:
|
||||
decoded_segments = base64.b64decode(encoded_segments)
|
||||
segments = pickle.loads(decoded_segments)
|
||||
self.service.console.print(
|
||||
DevtoolsLogMessage(
|
||||
DevConsoleLog(
|
||||
segments=segments,
|
||||
path=path,
|
||||
line_number=line_number,
|
||||
@@ -176,7 +181,7 @@ class ClientHandler:
|
||||
)
|
||||
elif type == "client_spillover":
|
||||
spillover = int(message_json["payload"]["spillover"])
|
||||
info_renderable = DevtoolsInternalMessage(
|
||||
info_renderable = DevConsoleNotice(
|
||||
f"Discarded {spillover} messages", level="warning"
|
||||
)
|
||||
self.service.console.print(info_renderable)
|
||||
@@ -198,9 +203,7 @@ class ClientHandler:
|
||||
|
||||
if self.request.remote:
|
||||
self.service.console.print(
|
||||
DevtoolsInternalMessage(
|
||||
f"Client '{escape(self.request.remote)}' connected"
|
||||
)
|
||||
DevConsoleNotice(f"Client '{escape(self.request.remote)}' connected")
|
||||
)
|
||||
try:
|
||||
await self.service.send_server_info(client_handler=self)
|
||||
@@ -223,20 +226,16 @@ class ClientHandler:
|
||||
await self.incoming_queue.put(message_json)
|
||||
elif message.type == WSMsgType.ERROR:
|
||||
self.service.console.print(
|
||||
DevtoolsInternalMessage(
|
||||
"Websocket error occurred", level="error"
|
||||
)
|
||||
DevConsoleNotice("Websocket error occurred", level="error")
|
||||
)
|
||||
break
|
||||
except Exception as error:
|
||||
self.service.console.print(
|
||||
DevtoolsInternalMessage(str(error), level="error")
|
||||
)
|
||||
self.service.console.print(DevConsoleNotice(str(error), level="error"))
|
||||
finally:
|
||||
if self.request.remote:
|
||||
self.service.console.print(
|
||||
"\n",
|
||||
DevtoolsInternalMessage(
|
||||
DevConsoleNotice(
|
||||
f"Client '{escape(self.request.remote)}' disconnected"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ Functions and classes to manage terminal geometry (anything involving coordinate
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from math import sqrt
|
||||
from typing import Any, cast, NamedTuple, Tuple, Union, TypeVar
|
||||
|
||||
SpacingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
|
||||
@@ -94,7 +93,7 @@ class Offset(NamedTuple):
|
||||
"""
|
||||
x1, y1 = self
|
||||
x2, y2 = other
|
||||
distance = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
|
||||
distance = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) ** 0.5
|
||||
return distance
|
||||
|
||||
|
||||
@@ -665,5 +664,14 @@ class Spacing(NamedTuple):
|
||||
)
|
||||
return NotImplemented
|
||||
|
||||
def __sub__(self, other: object) -> Spacing:
|
||||
if isinstance(other, tuple):
|
||||
top1, right1, bottom1, left1 = self
|
||||
top2, right2, bottom2, left2 = other
|
||||
return Spacing(
|
||||
top1 - top2, right1 - right2, bottom1 - bottom2, left1 - left2
|
||||
)
|
||||
return NotImplemented
|
||||
|
||||
|
||||
NULL_OFFSET = Offset(0, 0)
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from operator import attrgetter
|
||||
from typing import Iterable, TYPE_CHECKING, NamedTuple, Sequence
|
||||
|
||||
from .._layout_resolve import layout_resolve
|
||||
@@ -91,7 +92,7 @@ class DockLayout(Layout):
|
||||
add_placement = placements.append
|
||||
arranged_widgets: set[Widget] = set()
|
||||
|
||||
for edge, widgets, z in docks:
|
||||
for z, (edge, widgets, _z) in enumerate(sorted(docks, key=attrgetter("z"))):
|
||||
|
||||
arranged_widgets.update(widgets)
|
||||
dock_options = [make_dock_options(widget, edge) for widget in widgets]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
from textual.geometry import Size, Offset, Region
|
||||
from textual.layout import Layout, WidgetPlacement
|
||||
@@ -24,15 +25,30 @@ class HorizontalLayout(Layout):
|
||||
x = max_width = max_height = 0
|
||||
parent_size = parent.size
|
||||
|
||||
for widget in parent.children:
|
||||
(content_width, content_height), margin = widget.styles.get_box_model(
|
||||
size, parent_size
|
||||
)
|
||||
region = Region(margin.left + x, margin.top, content_width, content_height)
|
||||
max_height = max(max_height, content_height + margin.height)
|
||||
box_models = [
|
||||
widget.get_box_model(size, parent_size)
|
||||
for widget in cast("list[Widget]", parent.children)
|
||||
]
|
||||
|
||||
margins = [
|
||||
max((box1.margin.right, box2.margin.left))
|
||||
for box1, box2 in zip(box_models, box_models[1:])
|
||||
]
|
||||
if box_models:
|
||||
margins.append(box_models[-1].margin.right)
|
||||
|
||||
x = box_models[0].margin.left if box_models else 0
|
||||
|
||||
for widget, box_model, margin in zip(parent.children, box_models, margins):
|
||||
content_width, content_height = box_model.size
|
||||
offset_y = widget.styles.align_height(content_height, parent_size.height)
|
||||
region = Region(x, offset_y, content_width, content_height)
|
||||
max_height = max(max_height, content_height)
|
||||
add_placement(WidgetPlacement(region, widget, 0))
|
||||
x += region.width + margin.left
|
||||
max_width = x + margin.right
|
||||
x += region.width + margin
|
||||
max_width = x
|
||||
|
||||
max_width += margins[-1] if margins else 0
|
||||
|
||||
total_region = Region(0, 0, max_width, max_height)
|
||||
add_placement(WidgetPlacement(total_region, None, 0))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import cast, TYPE_CHECKING
|
||||
|
||||
from .. import log
|
||||
|
||||
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class VerticalLayout(Layout):
|
||||
"""Simple vertical layout."""
|
||||
"""Used to layout Widgets vertically on screen, from top to bottom."""
|
||||
|
||||
name = "vertical"
|
||||
|
||||
@@ -26,15 +26,30 @@ class VerticalLayout(Layout):
|
||||
y = max_width = max_height = 0
|
||||
parent_size = parent.size
|
||||
|
||||
for widget in parent.children:
|
||||
(content_width, content_height), margin = widget.styles.get_box_model(
|
||||
size, parent_size
|
||||
)
|
||||
region = Region(margin.left, y + margin.top, content_width, content_height)
|
||||
max_width = max(max_width, content_width + margin.width)
|
||||
box_models = [
|
||||
widget.get_box_model(size, parent_size)
|
||||
for widget in cast("list[Widget]", parent.children)
|
||||
]
|
||||
|
||||
margins = [
|
||||
max((box1.margin.bottom, box2.margin.top))
|
||||
for box1, box2 in zip(box_models, box_models[1:])
|
||||
]
|
||||
if box_models:
|
||||
margins.append(box_models[-1].margin.bottom)
|
||||
|
||||
y = box_models[0].margin.top if box_models else 0
|
||||
|
||||
for widget, box_model, margin in zip(parent.children, box_models, margins):
|
||||
content_width, content_height = box_model.size
|
||||
offset_x = widget.styles.align_width(content_width, parent_size.width)
|
||||
region = Region(offset_x, y, content_width, content_height)
|
||||
max_height = max(max_height, content_height)
|
||||
add_placement(WidgetPlacement(region, widget, 0))
|
||||
y += region.height + margin.top
|
||||
max_height = y + margin.bottom
|
||||
y += region.height + margin
|
||||
max_height = y
|
||||
|
||||
max_height += margins[-1] if margins else 0
|
||||
|
||||
total_region = Region(0, 0, max_width, max_height)
|
||||
add_placement(WidgetPlacement(total_region, None, 0))
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.console import ConsoleOptions, Console, RenderResult
|
||||
from rich.color import Color
|
||||
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
|
||||
from ._blend_colors import blend_colors_rgb
|
||||
from ..color import Color
|
||||
|
||||
|
||||
class VerticalGradient:
|
||||
"""Draw a vertical gradient."""
|
||||
|
||||
def __init__(self, color1: str, color2: str) -> None:
|
||||
self._color1 = Color.parse(color1).get_truecolor()
|
||||
self._color2 = Color.parse(color2).get_truecolor()
|
||||
self._color1 = Color.parse(color1)
|
||||
self._color2 = Color.parse(color2)
|
||||
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
@@ -22,15 +22,20 @@ class VerticalGradient:
|
||||
height = options.height or options.max_height
|
||||
color1 = self._color1
|
||||
color2 = self._color2
|
||||
default_color = Color.default()
|
||||
default_color = Color(0, 0, 0).rich_color
|
||||
from_color = Style.from_color
|
||||
blend = color1.blend
|
||||
rich_color1 = color1.rich_color
|
||||
for y in range(height):
|
||||
yield Segment(
|
||||
f"{width * ' '}\n",
|
||||
from_color(
|
||||
default_color, blend_colors_rgb(color1, color2, y / (height - 1))
|
||||
line_color = from_color(
|
||||
default_color,
|
||||
(
|
||||
blend(color2, y / (height - 1)).rich_color
|
||||
if height > 1
|
||||
else rich_color1
|
||||
),
|
||||
)
|
||||
yield Segment(f"{width * ' '}\n", line_color)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -131,7 +131,8 @@ class Screen(Widget):
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
self.app.panic(error)
|
||||
self.app.on_exception(error)
|
||||
return
|
||||
self.app.refresh()
|
||||
self._dirty_widgets.clear()
|
||||
|
||||
@@ -204,6 +205,10 @@ class Screen(Widget):
|
||||
if isinstance(event, events.MouseDown) and widget.can_focus:
|
||||
await self.app.set_focus(widget)
|
||||
event.style = self.get_style_at(event.screen_x, event.screen_y)
|
||||
if widget is self:
|
||||
event.set_forwarded()
|
||||
await self.post_message(event)
|
||||
else:
|
||||
await widget.forward_event(event.offset(-region.x, -region.y))
|
||||
|
||||
elif isinstance(event, (events.MouseScrollDown, events.MouseScrollUp)):
|
||||
@@ -213,6 +218,9 @@ class Screen(Widget):
|
||||
return
|
||||
scroll_widget = widget
|
||||
if scroll_widget is not None:
|
||||
if scroll_widget is self:
|
||||
await self.post_message(event)
|
||||
else:
|
||||
await scroll_widget.forward_event(event)
|
||||
else:
|
||||
await self.post_message(event)
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import (
|
||||
Awaitable,
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Iterable,
|
||||
NamedTuple,
|
||||
cast,
|
||||
@@ -15,22 +14,23 @@ from typing import (
|
||||
import rich.repr
|
||||
from rich.align import Align
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.measure import Measurement
|
||||
from rich.padding import Padding
|
||||
from rich.pretty import Pretty
|
||||
from rich.style import Style
|
||||
from rich.styled import Styled
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
from . import errors, log
|
||||
from . import events
|
||||
from ._animator import BoundAnimator
|
||||
from ._border import Border
|
||||
from .box_model import BoxModel, get_box_model
|
||||
from ._callback import invoke
|
||||
from .color import Color
|
||||
from ._context import active_app
|
||||
from ._types import Lines
|
||||
from .dom import DOMNode
|
||||
from .geometry import clamp, Offset, Region, Size
|
||||
from .geometry import clamp, Offset, Region, Size, Spacing
|
||||
from .message import Message
|
||||
from . import messages
|
||||
from .layout import Layout
|
||||
@@ -96,6 +96,8 @@ class Widget(DOMNode):
|
||||
super().__init__(name=name, id=id, classes=classes)
|
||||
self.add_children(*children)
|
||||
|
||||
auto_width = Reactive(True)
|
||||
auto_height = Reactive(True)
|
||||
has_focus = Reactive(False)
|
||||
mouse_over = Reactive(False)
|
||||
scroll_x = Reactive(0.0, repaint=False)
|
||||
@@ -105,6 +107,34 @@ class Widget(DOMNode):
|
||||
show_vertical_scrollbar = Reactive(False, layout=True)
|
||||
show_horizontal_scrollbar = Reactive(False, layout=True)
|
||||
|
||||
def get_box_model(self, container: Size, viewport: Size) -> BoxModel:
|
||||
"""Process the box model for this widget.
|
||||
|
||||
Args:
|
||||
container (Size): The size of the container widget (with a layout)
|
||||
viewport (Size): The viewport size.
|
||||
|
||||
Returns:
|
||||
BoxModel: The size and margin for this widget.
|
||||
"""
|
||||
box_model = get_box_model(
|
||||
self.styles,
|
||||
container,
|
||||
viewport,
|
||||
self.get_content_width,
|
||||
self.get_content_height,
|
||||
)
|
||||
return box_model
|
||||
|
||||
def get_content_width(self, container_size: Size, parent_size: Size) -> int:
|
||||
console = self.app.console
|
||||
renderable = self.render()
|
||||
measurement = Measurement.get(console, console.options, renderable)
|
||||
return measurement.maximum
|
||||
|
||||
def get_content_height(self, container_size: Size, parent_size: Size) -> int:
|
||||
return container_size.height
|
||||
|
||||
async def watch_scroll_x(self, new_value: float) -> None:
|
||||
self.horizontal_scrollbar.position = int(new_value)
|
||||
|
||||
@@ -394,10 +424,7 @@ class Widget(DOMNode):
|
||||
if renderable_text_style:
|
||||
renderable = Styled(renderable, renderable_text_style)
|
||||
|
||||
if styles.padding:
|
||||
renderable = Padding(
|
||||
renderable, styles.padding, style=renderable_text_style
|
||||
)
|
||||
renderable = Padding(renderable, styles.padding, style=renderable_text_style)
|
||||
|
||||
if styles.border:
|
||||
renderable = Border(
|
||||
@@ -518,7 +545,9 @@ class Widget(DOMNode):
|
||||
"""Render all lines."""
|
||||
width, height = self.size
|
||||
renderable = self.render_styled()
|
||||
options = self.console.options.update_dimensions(width, height)
|
||||
options = self.console.options.update_dimensions(width, height).update(
|
||||
highlight=False
|
||||
)
|
||||
lines = self.console.render_lines(renderable, options)
|
||||
self._render_cache = RenderCache(self.size, lines)
|
||||
self._dirty_regions.clear()
|
||||
@@ -656,12 +685,12 @@ class Widget(DOMNode):
|
||||
|
||||
def on_mouse_scroll_down(self, event) -> None:
|
||||
if self.is_container:
|
||||
if not self.scroll_down(animate=False):
|
||||
self.scroll_down(animate=False)
|
||||
event.stop()
|
||||
|
||||
def on_mouse_scroll_up(self, event) -> None:
|
||||
if self.is_container:
|
||||
if not self.scroll_up(animate=False):
|
||||
self.scroll_up(animate=False)
|
||||
event.stop()
|
||||
|
||||
def handle_scroll_to(self, message: ScrollTo) -> None:
|
||||
|
||||
10
tests/cli/test_cli.py
Normal file
10
tests/cli/test_cli.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from click.testing import CliRunner
|
||||
from importlib_metadata import version
|
||||
|
||||
from textual.cli.cli import run
|
||||
|
||||
|
||||
def test_cli_version():
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(run, ["--version"])
|
||||
assert version("textual") in result.output
|
||||
@@ -7,7 +7,7 @@ from rich.console import Console
|
||||
from rich.segment import Segment
|
||||
|
||||
from tests.utilities.render import wait_for_predicate
|
||||
from textual.devtools.renderables import DevtoolsLogMessage, DevtoolsInternalMessage
|
||||
from textual.devtools.renderables import DevConsoleLog, DevConsoleNotice
|
||||
|
||||
TIMESTAMP = 1649166819
|
||||
WIDTH = 40
|
||||
@@ -31,7 +31,7 @@ def console():
|
||||
|
||||
@time_machine.travel(TIMESTAMP)
|
||||
def test_log_message_render(console):
|
||||
message = DevtoolsLogMessage(
|
||||
message = DevConsoleLog(
|
||||
[Segment("content")],
|
||||
path="abc/hello.py",
|
||||
line_number=123,
|
||||
@@ -56,13 +56,13 @@ def test_log_message_render(console):
|
||||
timezone_name = local_time.tzname()
|
||||
string_timestamp = local_time.time()
|
||||
|
||||
assert left == f" [#888177]{string_timestamp} [dim]{timezone_name}[/]"
|
||||
assert left == f"[dim]{string_timestamp} {timezone_name}"
|
||||
assert right.align == "right"
|
||||
assert "hello.py:123" in right.renderable
|
||||
|
||||
|
||||
def test_internal_message_render(console):
|
||||
message = DevtoolsInternalMessage("hello")
|
||||
message = DevConsoleNotice("hello")
|
||||
rule = next(iter(message.__rich_console__(console, console.options)))
|
||||
assert rule.title == "hello"
|
||||
assert rule.characters == "─"
|
||||
|
||||
183
tests/test_box_model.py
Normal file
183
tests/test_box_model.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from textual.box_model import BoxModel, get_box_model
|
||||
from textual.css.styles import Styles
|
||||
from textual.geometry import Size, Spacing
|
||||
|
||||
|
||||
def test_content_box():
|
||||
styles = Styles()
|
||||
styles.width = 10
|
||||
styles.height = 8
|
||||
styles.padding = 1
|
||||
styles.border = ("solid", "red")
|
||||
|
||||
# border-box is default
|
||||
assert styles.box_sizing == "border-box"
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
# Size should be inclusive of padding / border
|
||||
assert box_model == BoxModel(Size(10, 8), Spacing(0, 0, 0, 0))
|
||||
|
||||
# Switch to content-box
|
||||
styles.box_sizing = "content-box"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
# width and height have added padding / border to accommodate content
|
||||
assert box_model == BoxModel(Size(14, 12), Spacing(0, 0, 0, 0))
|
||||
|
||||
|
||||
def test_width():
|
||||
"""Test width settings."""
|
||||
styles = Styles()
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 20), Spacing(0, 0, 0, 0))
|
||||
|
||||
# Add a margin and check that it is reported
|
||||
styles.margin = (1, 2, 3, 4)
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set width to auto-detect
|
||||
styles.width = "auto"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
# Setting width to auto should call get_auto_width
|
||||
assert box_model == BoxModel(Size(10, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set width to 100 vw which should make it the width of the parent
|
||||
styles.width = "100vw"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(80, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set the width to 100% should make it fill the container size
|
||||
styles.width = "100%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
styles.width = "100vw"
|
||||
styles.max_width = "50%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(30, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
|
||||
def test_height():
|
||||
"""Test width settings."""
|
||||
styles = Styles()
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 20), Spacing(0, 0, 0, 0))
|
||||
|
||||
# Add a margin and check that it is reported
|
||||
styles.margin = (1, 2, 3, 4)
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set width to 100 vw which should make it the width of the parent
|
||||
styles.height = "100vh"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 24), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set the width to 100% should make it fill the container size
|
||||
styles.height = "100%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 20), Spacing(1, 2, 3, 4))
|
||||
|
||||
styles.height = "100vh"
|
||||
styles.max_height = "50%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(60, 10), Spacing(1, 2, 3, 4))
|
||||
|
||||
|
||||
def test_max():
|
||||
"""Check that max_width and max_height are respected."""
|
||||
styles = Styles()
|
||||
styles.width = 100
|
||||
styles.height = 80
|
||||
styles.max_width = 40
|
||||
styles.max_height = 30
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(40, 30), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(40, 30), Spacing(0, 0, 0, 0))
|
||||
|
||||
|
||||
def test_min():
|
||||
"""Check that min_width and min_height are respected."""
|
||||
styles = Styles()
|
||||
styles.width = 10
|
||||
styles.height = 5
|
||||
styles.min_width = 40
|
||||
styles.min_height = 30
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(40, 30), Size(80, 24), get_auto_width, get_auto_height
|
||||
)
|
||||
assert box_model == BoxModel(Size(40, 30), Spacing(0, 0, 0, 0))
|
||||
@@ -317,6 +317,13 @@ def test_spacing_add():
|
||||
Spacing(1, 2, 3, 4) + "foo"
|
||||
|
||||
|
||||
def test_spacing_sub():
|
||||
assert Spacing(1, 2, 3, 4) - Spacing(5, 6, 7, 8) == Spacing(-4, -4, -4, -4)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
Spacing(1, 2, 3, 4) - "foo"
|
||||
|
||||
|
||||
def test_split():
|
||||
assert Region(10, 5, 22, 15).split(10, 5) == (
|
||||
Region(10, 5, 10, 5),
|
||||
|
||||
Reference in New Issue
Block a user