diff --git a/poetry.lock b/poetry.lock index 40dc121e3..06bc5f132 100644 --- a/poetry.lock +++ b/poetry.lock @@ -72,7 +72,7 @@ python-versions = ">=3.6.1" [[package]] name = "click" -version = "8.0.3" +version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -103,7 +103,7 @@ test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] [[package]] name = "coverage" -version = "6.3.1" +version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -148,7 +148,7 @@ dev = ["twine", "markdown", "flake8", "wheel"] [[package]] name = "identify" -version = "2.4.10" +version = "2.4.11" description = "File identification library for Python" category = "dev" optional = false @@ -304,21 +304,21 @@ pytkdocs = ">=0.14.0" [[package]] name = "mypy" -version = "0.910" +version = "0.931" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typed-ast = {version = ">=1.4.0,<1.5.0", markers = "python_version < \"3.8\""} -typing-extensions = ">=3.7.4" +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] [[package]] name = "mypy-extensions" @@ -357,7 +357,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "platformdirs" -version = "2.5.0" +version = "2.5.1" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false @@ -488,11 +488,11 @@ six = ">=1.5" [[package]] name = "pytkdocs" -version = "0.15.0" +version = "0.16.0" description = "Load Python objects documentation." category = "dev" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" [package.dependencies] astunparse = {version = ">=1.6", markers = "python_version < \"3.9\""} @@ -564,11 +564,11 @@ python-versions = ">=3.7" [[package]] name = "typed-ast" -version = "1.4.3" +version = "1.5.2" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "typing-extensions" @@ -580,7 +580,7 @@ python-versions = "*" [[package]] name = "virtualenv" -version = "20.13.1" +version = "20.13.2" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -623,7 +623,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "3e7523fa7dbaa4f8c78acc4189fae9bee5b5ecdcca4d437027c7068283634121" +content-hash = "017cd9bd80c2432f3de493d190c162e98894d9bb1db0482502658e2bc9231887" [metadata.files] astunparse = [ @@ -672,8 +672,8 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -684,47 +684,47 @@ commonmark = [ {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] distlib = [ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, @@ -739,8 +739,8 @@ ghp-import = [ {file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"}, ] identify = [ - {file = "identify-2.4.10-py2.py3-none-any.whl", hash = "sha256:7d10baf6ba6f1912a0a49f4c1c2c49fa1718765c3a37d72d13b07779567c5b85"}, - {file = "identify-2.4.10.tar.gz", hash = "sha256:e12b2aea3cf108de73ae055c2260783bde6601de09718f6768cf8e9f6f6322a6"}, + {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"}, + {file = "identify-2.4.11.tar.gz", hash = "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd"}, ] importlib-metadata = [ {file = "importlib_metadata-4.11.1-py3-none-any.whl", hash = "sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094"}, @@ -825,29 +825,26 @@ mkdocstrings = [ {file = "mkdocstrings-0.17.0.tar.gz", hash = "sha256:75b5cfa2039aeaf3a5f5cf0aa438507b0330ce76c8478da149d692daa7213a98"}, ] mypy = [ - {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, - {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, - {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, - {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, - {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, - {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, - {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, - {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, - {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, - {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, - {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, - {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, - {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, - {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, - {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, - {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, - {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, - {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, - {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, - {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, - {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, - {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, - {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -866,8 +863,8 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] platformdirs = [ - {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"}, - {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"}, + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -906,8 +903,8 @@ python-dateutil = [ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] pytkdocs = [ - {file = "pytkdocs-0.15.0-py3-none-any.whl", hash = "sha256:d6b2aec34448ec89acb8c1c25062cc1e70c6b26395d46fc7ee753b7e5a4e736a"}, - {file = "pytkdocs-0.15.0.tar.gz", hash = "sha256:4b45af89d6fa5fa50f979b0f9f54539286b84e245c81991bb838149aa2d9d9c9"}, + {file = "pytkdocs-0.16.0-py3-none-any.whl", hash = "sha256:098405ff347797cfb887a4d78f161372109bfbfffa0f8ee8daa5e2b8a8caba4b"}, + {file = "pytkdocs-0.16.0.tar.gz", hash = "sha256:274407bcefec58d7e411adb03b84da49120107912b1697d17d4a3aea0583f388"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, @@ -965,36 +962,30 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, + {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"}, ] typing-extensions = [ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, @@ -1002,8 +993,8 @@ typing-extensions = [ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] virtualenv = [ - {file = "virtualenv-20.13.1-py2.py3-none-any.whl", hash = "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7"}, - {file = "virtualenv-20.13.1.tar.gz", hash = "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14"}, + {file = "virtualenv-20.13.2-py2.py3-none-any.whl", hash = "sha256:e7b34c9474e6476ee208c43a4d9ac1510b041c68347eabfe9a9ea0c86aa0a46b"}, + {file = "virtualenv-20.13.2.tar.gz", hash = "sha256:01f5f80744d24a3743ce61858123488e91cb2dd1d3bdf92adaf1bba39ffdedf0"}, ] watchdog = [ {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, diff --git a/pyproject.toml b/pyproject.toml index 62e4db4bf..7e8756cc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ typing-extensions = { version = "^3.10.0", python = "<3.8" } [tool.poetry.dev-dependencies] pytest = "^6.2.3" black = "^22.1.0" -mypy = "^0.910" +mypy = "^0.931" pytest-cov = "^2.12.1" mkdocs = "^1.2.3" mkdocstrings = "^0.17.0" diff --git a/src/textual/_arrangement.py b/src/textual/_arrangement.py new file mode 100644 index 000000000..17541eacb --- /dev/null +++ b/src/textual/_arrangement.py @@ -0,0 +1,477 @@ +from __future__ import annotations + +from itertools import chain +from operator import attrgetter, itemgetter +import sys +from typing import Iterator, Iterable, NamedTuple, TYPE_CHECKING + +import rich.repr +from rich.console import Console, ConsoleOptions, RenderResult +from rich.control import Control +from rich.segment import Segment, SegmentLines +from rich.style import Style + +from . import log +from .geometry import Region, Offset, Size + +from .layout import WidgetPlacement +from ._loop import loop_last +from ._types import Lines + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: # pragma: no cover + from typing_extensions import TypeAlias + + +if TYPE_CHECKING: + from .widget import Widget + + +class NoWidget(Exception): + """Raised when there is no widget at the requested coordinate.""" + + +class ReflowResult(NamedTuple): + """The result of a reflow operation. Describes the chances to widgets.""" + + hidden: set[Widget] + shown: set[Widget] + resized: set[Widget] + + +class RenderRegion(NamedTuple): + """Defines the absolute location of a Widget.""" + + region: Region + order: tuple[int, ...] + clip: Region + + +RenderRegionMap: TypeAlias = dict[Widget, RenderRegion] + + +@rich.repr.auto +class LayoutUpdate: + """A renderable containing the result of a render for a given region.""" + + def __init__(self, lines: Lines, region: Region) -> None: + self.lines = lines + self.region = region + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + yield Control.home() + x = self.region.x + new_line = Segment.line() + move_to = Control.move_to + for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)): + yield move_to(x, y) + yield from line + if not last: + yield new_line + + def __rich_repr__(self) -> rich.repr.Result: + x, y, width, height = self.region + yield "x", x + yield "y", y + yield "width", width + yield "height", height + + +@rich.repr.auto(angular=True) +class Arrangement: + """Responsible for storing information regarding the relative positions of Widgets and rendering them.""" + + def __init__(self) -> None: + # A mapping of Widget on to its "render location" (absolute position / depth) + self.map: RenderRegionMap = {} + + # All widgets considered in the arrangement + # Not this may be a supperset of self.map.keys() as some widgets may be invisible for various reasons + self.widgets: set[Widget] = set() + + # Dimensions of the arrangement + self.width = 0 + self.height = 0 + + self.regions: dict[Widget, tuple[Region, Region]] = {} + self._cuts: list[list[int]] | None = None + self._require_update: bool = True + self.background = "" + + def __rich_repr__(self) -> rich.repr.Result: + yield "width", self.width + yield "height", self.height + yield "widgets", self.widgets + + def check_update(self) -> bool: + return self._require_update + + def require_update(self) -> None: + self._require_update = True + self.reset() + self.map.clear() + self.widgets.clear() + + def reset_update(self) -> None: + self._require_update = False + + def reset(self) -> None: + self._cuts = None + + def reflow(self, parent: Widget, size: Size) -> ReflowResult: + """Reflow (layout) widget and its children. + + Args: + parent (Widget): The root widget. + size (Size): Size of the area to be filled. + + Returns: + ReflowResult: Hidden shown and resized widgets + """ + self.reset() + + self.width = size.width + self.height = size.height + + map, virtual_size = self._arrange_root(parent) + + self._require_update = False + + old_widgets = set(self.map.keys()) + new_widgets = set(map.keys()) + # Newly visible widgets + shown_widgets = new_widgets - old_widgets + # Newly hidden widgets + hidden_widgets = old_widgets - new_widgets + + self._layout_map = map + + # Copy renders if the size hasn't changed + new_renders = { + widget: (region, clip) for widget, (region, _order, clip) in map.items() + } + self.regions = new_renders + + # Widgets with changed size + resized_widgets = { + widget + for widget, (region, *_) in map.items() + if widget in old_widgets and widget.size != region.size + } + + parent.virtual_size = virtual_size + + return ReflowResult( + hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets + ) + + def _arrange_root(self, root: Widget) -> tuple[RenderRegionMap, Size]: + """Arrange a widgets children based on its layout attribute. + + Args: + root (Widget): Top level widget. + + Returns: + map[dict[Widget, RenderRegion], Size]: A mapping of widget on to render region + and the "virtual size" (scrollable reason) + """ + size = Size(self.width, self.height) + ORIGIN = Offset(0, 0) + + map: dict[Widget, RenderRegion] = {} + + def add_widget( + widget, + region: Region, + order: tuple[int, ...], + clip: Region, + ): + widgets: set[Widget] = set() + styles_offset = widget.styles.offset + total_region = region + layout_offset = ( + styles_offset.resolve(region.size, clip.size) + if styles_offset + else ORIGIN + ) + + map[widget] = RenderRegion(region + layout_offset, order, clip) + + if widget.layout is not None: + scroll = widget.scroll + total_region = region.size.region + sub_clip = clip.intersection(region) + + placements: list[WidgetPlacement] = [] + add_placement = placements.append + iter_arrange = iter(widget.layout.arrange(widget, region.size, scroll)) + try: + while True: + add_placement(iter_arrange.__next__()) + except StopIteration as stop_iteration: + widgets.update(stop_iteration.value) + + placements = sorted( + [ + placement.apply_margin() + for placement in widget.layout.arrange( + widget, region.size, scroll + ) + ], + key=attrgetter("order"), + ) + for sub_region, sub_widget, z in placements: + total_region = total_region.union(sub_region) + if sub_widget is not None: + add_widget( + sub_widget, + sub_region + region.origin - scroll, + sub_widget.z + (z,), + sub_clip, + ) + return total_region.size + + virtual_size = add_widget(root, size.region, (), size.region) + return map, virtual_size + + async def mount_all(self, view: "View") -> None: + view.mount(*self.widgets) + + def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]: + layers = sorted(self.map.items(), key=lambda item: item[1].order, reverse=True) + for widget, (region, order, clip) in layers: + yield widget, region.intersection(clip), region + + def get_offset(self, widget: Widget) -> Offset: + """Get the offset of a widget.""" + try: + return self.map[widget].region.origin + except KeyError: + raise NoWidget("Widget is not in layout") + + def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]: + """Get the widget under the given point or None.""" + for widget, cropped_region, region in self: + if widget.is_visual and cropped_region.contains(x, y): + return widget, region + raise NoWidget(f"No widget under screen coordinate ({x}, {y})") + + def get_style_at(self, x: int, y: int) -> Style: + """Get the Style at the given cell or Style.null() + + Args: + x (int): X position within the Layout + y (int): Y position within the Layout + + Returns: + Style: The Style at the cell (x, y) within the Layout + """ + try: + widget, region = self.get_widget_at(x, y) + except NoWidget: + return Style.null() + if widget not in self.regions: + return Style.null() + lines = widget._get_lines() + x -= region.x + y -= region.y + line = lines[y] + end = 0 + for segment in line: + end += segment.cell_length + if x < end: + return segment.style or Style.null() + return Style.null() + + def get_widget_region(self, widget: Widget) -> Region: + """Get the Region of a Widget contained in this Layout. + + Args: + widget (Widget): The Widget in this layout you wish to know the Region of. + + Raises: + NoWidget: If the Widget is not contained in this Layout. + + Returns: + Region: The Region of the Widget. + + """ + try: + region, *_ = self.map[widget] + except KeyError: + raise NoWidget("Widget is not in layout") + else: + return region + + @property + def cuts(self) -> list[list[int]]: + """Get vertical cuts. + + A cut is every point on a line where a widget starts or ends. + + Returns: + list[list[int]]: A list of cuts for every line. + """ + if self._cuts is not None: + return self._cuts + width = self.width + height = self.height + screen_region = Region(0, 0, width, height) + cuts_sets = [{0, width} for _ in range(height)] + + if self.map is not None: + for region, order, clip in self.map.values(): + region = region.intersection(clip) + if region and (region in screen_region): + region_cuts = region.x_extents + for y in region.y_range: + cuts_sets[y].update(region_cuts) + + # Sort the cuts for each line + self._cuts = [sorted(cut_set) for cut_set in cuts_sets] + return self._cuts + + def _get_renders(self, console: Console) -> Iterable[tuple[Region, Region, Lines]]: + _rich_traceback_guard = True + layout_map = self.map + + if layout_map: + widget_regions = sorted( + ( + (widget, region, order, clip) + for widget, (region, order, clip) in layout_map.items() + ), + key=itemgetter(2), + reverse=True, + ) + else: + widget_regions = [] + + for widget, region, _order, clip in widget_regions: + + if not (widget.is_visual and widget.visible): + continue + + lines = widget._get_lines() + + if region in clip: + yield region, clip, lines + elif clip.overlaps(region): + new_region = region.intersection(clip) + delta_x = new_region.x - region.x + delta_y = new_region.y - region.y + splits = [delta_x, delta_x + new_region.width] + lines = lines[delta_y : delta_y + new_region.height] + divide = Segment.divide + lines = [list(divide(line, splits))[1] for line in lines] + yield region, clip, lines + + @classmethod + def _assemble_chops( + cls, chops: list[dict[int, list[Segment] | None]] + ) -> Iterable[Iterable[Segment]]: + + from_iterable = chain.from_iterable + for bucket in chops: + yield from_iterable( + line for _, line in sorted(bucket.items()) if line is not None + ) + + def render( + self, + console: Console, + *, + crop: Region = None, + ) -> SegmentLines: + """Render a layout. + + Args: + console (Console): Console instance. + clip (Optional[Region]): Region to clip to. + + Returns: + SegmentLines: A renderable + """ + width = self.width + height = self.height + screen = Region(0, 0, width, height) + + crop_region = crop.intersection(screen) if crop else screen + + _Segment = Segment + divide = _Segment.divide + + # Maps each cut on to a list of segments + cuts = self.cuts + chops: list[dict[int, list[Segment] | None]] = [ + {cut: None for cut in cut_set} for cut_set in cuts + ] + + # TODO: Provide an option to update the background + background_style = console.get_style(self.background) + background_render = [ + [_Segment(" " * width, background_style)] for _ in range(height) + ] + # Go through all the renders in reverse order and fill buckets with no render + renders = list(self._get_renders(console)) + + for region, clip, lines in chain( + renders, [(screen, screen, background_render)] + ): + render_region = region.intersection(clip) + for y, line in zip(render_region.y_range, lines): + + first_cut, last_cut = render_region.x_extents + final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)] + + if len(final_cuts) == 2: + cut_segments = [line] + else: + render_x = render_region.x + relative_cuts = [cut - render_x for cut in final_cuts] + _, *cut_segments = divide(line, relative_cuts) + for cut, segments in zip(final_cuts, cut_segments): + if chops[y][cut] is None: + chops[y][cut] = segments + + # Assemble the cut renders in to lists of segments + crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners + output_lines = self._assemble_chops(chops[crop_y:crop_y2]) + + def width_view(line: list[Segment]) -> list[Segment]: + if line: + div_lines = list(divide(line, [crop_x, crop_x2])) + line = div_lines[1] if len(div_lines) > 1 else div_lines[0] + return line + + if crop is not None and (crop_x, crop_x2) != (0, self.width): + render_lines = [width_view(line) for line in output_lines] + else: + render_lines = list(output_lines) + + return SegmentLines(render_lines, new_lines=True) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + yield self.render(console) + + def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None: + if widget not in self.regions: + return None + + region, clip = self.regions[widget] + + if not region.size: + return None + + widget.clear_render_cache() + + update_region = region.intersection(clip) + update_lines = self.render(console, crop=update_region).lines + update = LayoutUpdate(update_lines, update_region) + log(update) + return update diff --git a/src/textual/app.py b/src/textual/app.py index 4dd9d5d2e..27cd90cc2 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -662,7 +662,7 @@ class App(DOMNode): async def handle_layout(self, message: messages.Layout) -> None: message.stop() - await self.view.refresh_layout() + # await self.view.refresh_layout() self.app.refresh() async def on_key(self, event: events.Key) -> None: diff --git a/src/textual/layout.py b/src/textual/layout.py index cd5549bbf..1c4cc56f3 100644 --- a/src/textual/layout.py +++ b/src/textual/layout.py @@ -1,24 +1,10 @@ from __future__ import annotations -import sys from abc import ABC, abstractmethod -from itertools import chain -from operator import itemgetter -from typing import ClassVar, Iterable, Iterator, NamedTuple, TYPE_CHECKING +from typing import ClassVar, Generator, Iterable, NamedTuple, TYPE_CHECKING -import rich.repr -from rich.console import Console, ConsoleOptions, RenderResult -from rich.control import Control -from rich.segment import Segment, SegmentLines -from rich.style import Style -from . import log -from ._loop import loop_last -from ._types import Lines from .geometry import Region, Offset, Size -from .layout_map import LayoutMap - -PY38 = sys.version_info >= (3, 8) if TYPE_CHECKING: @@ -26,18 +12,6 @@ if TYPE_CHECKING: from .view import View -class NoWidget(Exception): - """Raised when there is no widget at the requested coordinate.""" - - -class ReflowResult(NamedTuple): - """The result of a reflow operation. Describes the chances to widgets.""" - - hidden: set[Widget] - shown: set[Widget] - resized: set[Widget] - - class WidgetPlacement(NamedTuple): """The position, size, and relative order of a widget within its parent.""" @@ -66,364 +40,22 @@ class WidgetPlacement(NamedTuple): return self -@rich.repr.auto -class LayoutUpdate: - """A renderable containing the result of a render for a given region.""" - - def __init__(self, lines: Lines, region: Region) -> None: - self.lines = lines - self.region = region - - def __rich_console__( - self, console: Console, options: ConsoleOptions - ) -> RenderResult: - yield Control.home() - x = self.region.x - new_line = Segment.line() - move_to = Control.move_to - for last, (y, line) in loop_last(enumerate(self.lines, self.region.y)): - yield move_to(x, y) - yield from line - if not last: - yield new_line - - def __rich_repr__(self) -> rich.repr.Result: - x, y, width, height = self.region - yield "x", x - yield "y", y - yield "width", width - yield "height", height - - class Layout(ABC): """Responsible for arranging Widgets in a view and rendering them.""" name: ClassVar[str] = "" - def __init__(self) -> None: - self._layout_map: LayoutMap | None = None - self.width = 0 - self.height = 0 - self.regions: dict[Widget, tuple[Region, Region]] = {} - self._cuts: list[list[int]] | None = None - self._require_update: bool = True - self.background = "" - - def check_update(self) -> bool: - return self._require_update - - def require_update(self) -> None: - self._require_update = True - self.reset() - self._layout_map = None - - def reset_update(self) -> None: - self._require_update = False - - def reset(self) -> None: - self._cuts = None - - def reflow(self, view: View, size: Size) -> ReflowResult: - self.reset() - - self.width = size.width - self.height = size.height - - map = LayoutMap(size) - map.add_widget(view, size.region, (), size.region) - - self._require_update = False - - old_widgets = set() if self.map is None else set(self.map.keys()) - new_widgets = set(map.keys()) - # Newly visible widgets - shown_widgets = new_widgets - old_widgets - # Newly hidden widgets - hidden_widgets = old_widgets - new_widgets - - self._layout_map = map - - # Copy renders if the size hasn't changed - new_renders = { - widget: (region, clip) for widget, (region, _order, clip) in map.items() - } - self.regions = new_renders - - # Widgets with changed size - resized_widgets = { - widget - for widget, (region, *_) in map.items() - if widget in old_widgets and widget.size != region.size - } - - return ReflowResult( - hidden=hidden_widgets, shown=shown_widgets, resized=resized_widgets - ) - - @abstractmethod - def get_widgets(self, view: View) -> Iterable[Widget]: - ... - @abstractmethod def arrange( - self, view: View, size: Size, scroll: Offset - ) -> Iterable[WidgetPlacement]: + self, parent: View, size: Size, scroll: Offset + ) -> Generator[WidgetPlacement, None, set[Widget]]: """Generate a layout map that defines where on the screen the widgets will be drawn. Args: - view (View): The View instance. + parent (Widget): Parent widget. size (Size): Size of container. scroll (Offset): Offset to apply to the Widget placements. Returns: Iterable[WidgetPlacement]: An iterable of widget location """ - - async def mount_all(self, view: "View") -> None: - widgets = list(self.get_widgets(view)) - if widgets: - view.mount(*widgets) - - @property - def map(self) -> LayoutMap: - assert self._layout_map is not None - return self._layout_map - - def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]: - if self.map is not None: - layers = sorted( - self.map.widgets.items(), key=lambda item: item[1].order, reverse=True - ) - for widget, (region, order, clip) in layers: - yield widget, region.intersection(clip), region - - def get_offset(self, widget: Widget) -> Offset: - """Get the offset of a widget.""" - try: - return self.map[widget].region.origin - except KeyError: - raise NoWidget("Widget is not in layout") - - def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]: - """Get the widget under the given point or None.""" - for widget, cropped_region, region in self: - if widget.is_visual and cropped_region.contains(x, y): - return widget, region - raise NoWidget(f"No widget under screen coordinate ({x}, {y})") - - def get_style_at(self, x: int, y: int) -> Style: - """Get the Style at the given cell or Style.null() - - Args: - x (int): X position within the Layout - y (int): Y position within the Layout - - Returns: - Style: The Style at the cell (x, y) within the Layout - """ - try: - widget, region = self.get_widget_at(x, y) - except NoWidget: - return Style.null() - if widget not in self.regions: - return Style.null() - lines = widget._get_lines() - x -= region.x - y -= region.y - line = lines[y] - end = 0 - for segment in line: - end += segment.cell_length - if x < end: - return segment.style or Style.null() - return Style.null() - - def get_widget_region(self, widget: Widget) -> Region: - """Get the Region of a Widget contained in this Layout. - - Args: - widget (Widget): The Widget in this layout you wish to know the Region of. - - Raises: - NoWidget: If the Widget is not contained in this Layout. - - Returns: - Region: The Region of the Widget. - - """ - try: - region, *_ = self.map[widget] - except KeyError: - raise NoWidget("Widget is not in layout") - else: - return region - - @property - def cuts(self) -> list[list[int]]: - """Get vertical cuts. - - A cut is every point on a line where a widget starts or ends. - - Returns: - list[list[int]]: A list of cuts for every line. - """ - if self._cuts is not None: - return self._cuts - width = self.width - height = self.height - screen_region = Region(0, 0, width, height) - cuts_sets = [{0, width} for _ in range(height)] - - if self.map is not None: - for region, order, clip in self.map.values(): - region = region.intersection(clip) - if region and (region in screen_region): - region_cuts = region.x_extents - for y in region.y_range: - cuts_sets[y].update(region_cuts) - - # Sort the cuts for each line - self._cuts = [sorted(cut_set) for cut_set in cuts_sets] - return self._cuts - - def _get_renders(self, console: Console) -> Iterable[tuple[Region, Region, Lines]]: - _rich_traceback_guard = True - layout_map = self.map - - if layout_map: - widget_regions = sorted( - ( - (widget, region, order, clip) - for widget, (region, order, clip) in layout_map.items() - ), - key=itemgetter(2), - reverse=True, - ) - else: - widget_regions = [] - - for widget, region, _order, clip in widget_regions: - - if not (widget.is_visual and widget.visible): - continue - - lines = widget._get_lines() - - if region in clip: - yield region, clip, lines - elif clip.overlaps(region): - new_region = region.intersection(clip) - delta_x = new_region.x - region.x - delta_y = new_region.y - region.y - splits = [delta_x, delta_x + new_region.width] - lines = lines[delta_y : delta_y + new_region.height] - divide = Segment.divide - lines = [list(divide(line, splits))[1] for line in lines] - yield region, clip, lines - - @classmethod - def _assemble_chops( - cls, chops: list[dict[int, list[Segment] | None]] - ) -> Iterable[Iterable[Segment]]: - - from_iterable = chain.from_iterable - for bucket in chops: - yield from_iterable( - line for _, line in sorted(bucket.items()) if line is not None - ) - - def render( - self, - console: Console, - *, - crop: Region = None, - ) -> SegmentLines: - """Render a layout. - - Args: - console (Console): Console instance. - clip (Optional[Region]): Region to clip to. - - Returns: - SegmentLines: A renderable - """ - width = self.width - height = self.height - screen = Region(0, 0, width, height) - - crop_region = crop.intersection(screen) if crop else screen - - _Segment = Segment - divide = _Segment.divide - - # Maps each cut on to a list of segments - cuts = self.cuts - chops: list[dict[int, list[Segment] | None]] = [ - {cut: None for cut in cut_set} for cut_set in cuts - ] - - # TODO: Provide an option to update the background - background_style = console.get_style(self.background) - background_render = [ - [_Segment(" " * width, background_style)] for _ in range(height) - ] - # Go through all the renders in reverse order and fill buckets with no render - renders = list(self._get_renders(console)) - - for region, clip, lines in chain( - renders, [(screen, screen, background_render)] - ): - render_region = region.intersection(clip) - for y, line in zip(render_region.y_range, lines): - - first_cut, last_cut = render_region.x_extents - final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)] - - if len(final_cuts) == 2: - cut_segments = [line] - else: - render_x = render_region.x - relative_cuts = [cut - render_x for cut in final_cuts] - _, *cut_segments = divide(line, relative_cuts) - for cut, segments in zip(final_cuts, cut_segments): - if chops[y][cut] is None: - chops[y][cut] = segments - - # Assemble the cut renders in to lists of segments - crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners - output_lines = self._assemble_chops(chops[crop_y:crop_y2]) - - def width_view(line: list[Segment]) -> list[Segment]: - if line: - div_lines = list(divide(line, [crop_x, crop_x2])) - line = div_lines[1] if len(div_lines) > 1 else div_lines[0] - return line - - if crop is not None and (crop_x, crop_x2) != (0, self.width): - render_lines = [width_view(line) for line in output_lines] - else: - render_lines = list(output_lines) - - return SegmentLines(render_lines, new_lines=True) - - def __rich_console__( - self, console: Console, options: ConsoleOptions - ) -> RenderResult: - yield self.render(console) - - def update_widget(self, console: Console, widget: Widget) -> LayoutUpdate | None: - if widget not in self.regions: - return None - - region, clip = self.regions[widget] - - if not region.size: - return None - - widget.clear_render_cache() - - update_region = region.intersection(clip) - update_lines = self.render(console, crop=update_region).lines - update = LayoutUpdate(update_lines, update_region) - log(update) - return update diff --git a/src/textual/layout_map.py b/src/textual/layout_map.py index 1885c388b..4bf6464d6 100644 --- a/src/textual/layout_map.py +++ b/src/textual/layout_map.py @@ -1,3 +1,9 @@ +""" + +Planned for deprecation + +""" + from __future__ import annotations @@ -10,12 +16,16 @@ from .widget import Widget class RenderRegion(NamedTuple): + """Defines the absolute location of a Widget.""" + region: Region order: tuple[int, ...] clip: Region class LayoutMap: + """A container that maps widgets on to their absolute location.""" + def __init__(self, size: Size) -> None: self.size = size self.widgets: dict[Widget, RenderRegion] = {} @@ -53,6 +63,8 @@ class LayoutMap: self.widgets[widget] = RenderRegion(region + layout_offset, order, clip) + # TODO: replace with widget.layout + if isinstance(widget, View): view: View = widget scroll = view.scroll diff --git a/src/textual/layouts/dock.py b/src/textual/layouts/dock.py index d70d24a74..bd581d483 100644 --- a/src/textual/layouts/dock.py +++ b/src/textual/layouts/dock.py @@ -3,7 +3,7 @@ from __future__ import annotations import sys from collections import defaultdict from dataclasses import dataclass -from typing import Iterable, TYPE_CHECKING, NamedTuple, Sequence +from typing import Generator, Iterable, TYPE_CHECKING, NamedTuple, Sequence from .._layout_resolve import layout_resolve from ..css.types import Edge @@ -47,31 +47,27 @@ class DockLayout(Layout): def __repr__(self): return "" - def get_docks(self, view: View) -> list[Dock]: + def get_docks(self, parent: Widget) -> list[Dock]: groups: dict[str, list[Widget]] = defaultdict(list) - for child in view.children: + for child in parent.children: assert isinstance(child, Widget) if child.display: groups[child.styles.dock].append(child) docks: list[Dock] = [] append_dock = docks.append - for name, edge, z in view.styles.docks: + for name, edge, z in parent.styles.docks: append_dock(Dock(edge, groups[name], z)) return docks - def get_widgets(self, view: View) -> Iterable[Widget]: - for dock in self.get_docks(view): - yield from dock.widgets - def arrange( - self, view: View, size: Size, scroll: Offset - ) -> Iterable[WidgetPlacement]: + self, parent: Widget, size: Size, scroll: Offset + ) -> Generator[WidgetPlacement, None, set[Widget]]: width, height = size layout_region = Region(0, 0, width, height) layers: dict[int, Region] = defaultdict(lambda: layout_region) - docks = self.get_docks(view) + docks = self.get_docks(parent) def make_dock_options(widget, edge: Edge) -> DockOptions: styles = widget.styles @@ -93,11 +89,14 @@ class DockLayout(Layout): Placement = WidgetPlacement + arranged_widgets: set[Widget] = set() + for edge, widgets, z in docks: + arranged_widgets.update(widgets) dock_options = [make_dock_options(widget, edge) for widget in widgets] region = layers[z] - if not region: + if not region.area: # No space left continue @@ -168,3 +167,5 @@ class DockLayout(Layout): region = Region(x, y, width - total, height) layers[z] = region + + return arranged_widgets diff --git a/src/textual/view.py b/src/textual/view.py index 5651fc41e..1143312c2 100644 --- a/src/textual/view.py +++ b/src/textual/view.py @@ -1,250 +1,17 @@ from __future__ import annotations -from typing import Callable, Iterable +from .widget import Widget import rich.repr -from rich.console import RenderableType -from rich.style import Style - -from . import errors, events, messages -from .geometry import Size, Offset, Region -from .layout import Layout, NoWidget, WidgetPlacement -from .reactive import Reactive, watch -from .widget import Widget @rich.repr.auto class View(Widget): + """A widget for the root of the app.""" DEFAULT_STYLES = """ - layout: dock; - docks: _default=top; + + layout: dock + docks: _default=top; + """ - - def __init__(self, name: str | None = None, id: str | None = None) -> None: - self.mouse_over: Widget | None = None - self._mouse_style: Style = Style() - self._mouse_widget: Widget | None = None - - self._cached_arrangement: tuple[Size, Offset, list[WidgetPlacement]] = ( - Size(), - Offset(), - [], - ) - super().__init__(name=name, id=id) - - background: Reactive[str] = Reactive("") - scroll_x: Reactive[int] = Reactive(0) - scroll_y: Reactive[int] = Reactive(0) - virtual_size = Reactive(Size(0, 0)) - - async def watch_background(self, value: str) -> None: - self.layout.background = value - self.app.refresh() - - @property - def layout(self) -> Layout | None: - """Convenience property for accessing ``self.styles.layout``. - - Returns: The Layout associated with this view - """ - - return self.styles.layout - - @layout.setter - def layout(self, new_value: Layout) -> None: - """Convenience property setter for setting ``view.styles.layout``. - Args: - new_value: - - Returns: - None - """ - self.styles.layout = new_value - - @property - def scroll(self) -> Offset: - return Offset(self.scroll_x, self.scroll_y) - - def __rich_repr__(self) -> rich.repr.Result: - yield "name", self.name - - @property - def is_visual(self) -> bool: - return False - - @property - def is_root_view(self) -> bool: - return bool(self._parent and self.parent is self.app) - - def is_mounted(self, widget: Widget) -> bool: - return self.app.is_mounted(widget) - - def render(self) -> RenderableType: - return self.layout or "" - - def get_offset(self, widget: Widget) -> Offset: - return self.layout.get_offset(widget) - - def get_arrangement(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]: - cached_size, cached_scroll, arrangement = self._cached_arrangement - if cached_size == size and cached_scroll == scroll: - return arrangement - - placements = [ - placement.apply_margin() - for placement in self.layout.arrange(self, size, scroll) - ] - - self._cached_arrangement = (size, scroll, placements) - return placements - - async def handle_update(self, message: messages.Update) -> None: - if self.is_root_view: - message.stop() - widget = message.widget - assert isinstance(widget, Widget) - - display_update = self.layout.update_widget(self.console, widget) - if display_update is not None: - self.app.display(display_update) - - async def handle_layout(self, message: messages.Layout) -> None: - await self.refresh_layout() - if self.is_root_view: - message.stop() - self.app.refresh() - - def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None: - self.app.register(self, *anon_widgets, **widgets) - self.refresh() - - async def refresh_layout(self) -> None: - self._cached_arrangement = (Size(), Offset(), []) - try: - await self.layout.mount_all(self) - if not self.is_root_view: - await self.app.view.refresh_layout() - return - - if not self.size: - return - - hidden, shown, resized = self.layout.reflow(self, Size(*self.console.size)) - assert self.layout.map is not None - - for widget in hidden: - widget.post_message_no_wait(events.Hide(self)) - for widget in shown: - widget.post_message_no_wait(events.Show(self)) - - send_resize = shown - send_resize.update(resized) - - for widget, region, unclipped_region in self.layout: - widget._update_size(unclipped_region.size) - if widget in send_resize: - widget.post_message_no_wait( - events.Resize(self, unclipped_region.size) - ) - except Exception: - self.app.panic() - - async def on_resize(self, event: events.Resize) -> None: - self._update_size(event.size) - if self.is_root_view: - await self.refresh_layout() - self.app.refresh() - event.stop() - - def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]: - return self.layout.get_widget_at(x, y) - - def get_style_at(self, x: int, y: int) -> Style: - return self.layout.get_style_at(x, y) - - def get_widget_region(self, widget: Widget) -> Region: - return self.layout.get_widget_region(widget) - - async def on_mount(self, event: events.Mount) -> None: - async def watch_background(value: str) -> None: - self.background = value - - watch(self.app, "background", watch_background) - - async def on_idle(self, event: events.Idle) -> None: - if self.layout is None: - return - if self.layout.check_update(): - self.layout.reset_update() - await self.refresh_layout() - - async def _on_mouse_move(self, event: events.MouseMove) -> None: - - try: - if self.app.mouse_captured: - widget = self.app.mouse_captured - region = self.get_widget_region(widget) - else: - widget, region = self.get_widget_at(event.x, event.y) - except NoWidget: - await self.app.set_mouse_over(None) - else: - await self.app.set_mouse_over(widget) - await widget.forward_event( - events.MouseMove( - self, - event.x - region.x, - event.y - region.y, - event.delta_x, - event.delta_y, - event.button, - event.shift, - event.meta, - event.ctrl, - screen_x=event.screen_x, - screen_y=event.screen_y, - style=event.style, - ) - ) - - async def forward_event(self, event: events.Event) -> None: - event.set_forwarded() - if isinstance(event, (events.Enter, events.Leave)): - await self.post_message(event) - - elif isinstance(event, events.MouseMove): - event.style = self.get_style_at(event.screen_x, event.screen_y) - await self._on_mouse_move(event) - - elif isinstance(event, events.MouseEvent): - try: - if self.app.mouse_captured: - widget = self.app.mouse_captured - region = self.get_widget_region(widget) - else: - widget, region = self.get_widget_at(event.x, event.y) - except NoWidget: - pass - else: - 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) - await widget.forward_event(event.offset(-region.x, -region.y)) - - elif isinstance(event, (events.MouseScrollDown, events.MouseScrollUp)): - try: - widget, _region = self.get_widget_at(event.x, event.y) - except NoWidget: - return - scroll_widget = widget - if scroll_widget is not None: - await scroll_widget.forward_event(event) - else: - self.log("view.forwarded", event) - await self.post_message(event) - - async def action_toggle(self, name: str) -> None: - widget = self[name] - widget.visible = not widget.display - await self.post_message(messages.Layout(self)) diff --git a/src/textual/viewX.py b/src/textual/viewX.py new file mode 100644 index 000000000..8ef2e8df5 --- /dev/null +++ b/src/textual/viewX.py @@ -0,0 +1,253 @@ +from __future__ import annotations + +from typing import Callable, Iterable + +import rich.repr +from rich.console import RenderableType +from rich.style import Style + +from . import errors, events, messages +from ._arrangement import Arrangement +from .geometry import Size, Offset, Region +from .layout import Layout, NoWidget, WidgetPlacement +from .reactive import Reactive, watch +from .widget import Widget + + +@rich.repr.auto +class View(Widget): + + DEFAULT_STYLES = """ + layout: dock; + docks: _default=top; + """ + + def __init__(self, name: str | None = None, id: str | None = None) -> None: + self.mouse_over: Widget | None = None + self._mouse_style: Style = Style() + self._mouse_widget: Widget | None = None + + self._arrangement = Arrangement() + + self._cached_arrangement: tuple[Size, Offset, list[WidgetPlacement]] = ( + Size(), + Offset(), + [], + ) + super().__init__(name=name, id=id) + + background: Reactive[str] = Reactive("") + scroll_x: Reactive[int] = Reactive(0) + scroll_y: Reactive[int] = Reactive(0) + virtual_size = Reactive(Size(0, 0)) + + async def watch_background(self, value: str) -> None: + self._arrangement.background = value + self.app.refresh() + + @property + def layout(self) -> Layout | None: + """Convenience property for accessing ``self.styles.layout``. + + Returns: The Layout associated with this view + """ + + return self.styles.layout + + @layout.setter + def layout(self, new_value: Layout) -> None: + """Convenience property setter for setting ``view.styles.layout``. + Args: + new_value: + + Returns: + None + """ + self.styles.layout = new_value + + @property + def scroll(self) -> Offset: + return Offset(self.scroll_x, self.scroll_y) + + def __rich_repr__(self) -> rich.repr.Result: + yield "name", self.name + + @property + def is_visual(self) -> bool: + return False + + @property + def is_root_view(self) -> bool: + return bool(self._parent and self.parent is self.app) + + def is_mounted(self, widget: Widget) -> bool: + return self.app.is_mounted(widget) + + def render(self) -> RenderableType: + return self._arrangement + + def get_offset(self, widget: Widget) -> Offset: + return self._arrangement.get_offset(widget) + + def get_arrangement(self, size: Size, scroll: Offset) -> Iterable[WidgetPlacement]: + cached_size, cached_scroll, arrangement = self._cached_arrangement + if cached_size == size and cached_scroll == scroll: + return arrangement + + placements = [ + placement.apply_margin() + for placement in self.layout.arrange(self, size, scroll) + ] + + self._cached_arrangement = (size, scroll, placements) + return placements + + async def handle_update(self, message: messages.Update) -> None: + if self.is_root_view: + message.stop() + widget = message.widget + assert isinstance(widget, Widget) + + display_update = self.layout.update_widget(self.console, widget) + if display_update is not None: + self.app.display(display_update) + + async def handle_layout(self, message: messages.Layout) -> None: + await self.refresh_layout() + if self.is_root_view: + message.stop() + self.app.refresh() + + def mount(self, *anon_widgets: Widget, **widgets: Widget) -> None: + self.app.register(self, *anon_widgets, **widgets) + self.refresh() + + async def refresh_layout(self) -> None: + self._cached_arrangement = (Size(), Offset(), []) + try: + await self.layout.mount_all(self) + if not self.is_root_view: + await self.app.view.refresh_layout() + return + + if not self.size: + return + + hidden, shown, resized = self.layout.reflow(self, Size(*self.console.size)) + assert self.layout.map is not None + + for widget in hidden: + widget.post_message_no_wait(events.Hide(self)) + for widget in shown: + widget.post_message_no_wait(events.Show(self)) + + send_resize = shown + send_resize.update(resized) + + for widget, region, unclipped_region in self.layout: + widget._update_size(unclipped_region.size) + if widget in send_resize: + widget.post_message_no_wait( + events.Resize(self, unclipped_region.size) + ) + except Exception: + self.app.panic() + + async def on_resize(self, event: events.Resize) -> None: + self._update_size(event.size) + if self.is_root_view: + await self.refresh_layout() + self.app.refresh() + event.stop() + + def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]: + return self.layout.get_widget_at(x, y) + + def get_style_at(self, x: int, y: int) -> Style: + return self.layout.get_style_at(x, y) + + def get_widget_region(self, widget: Widget) -> Region: + return self.layout.get_widget_region(widget) + + async def on_mount(self, event: events.Mount) -> None: + async def watch_background(value: str) -> None: + self.background = value + + watch(self.app, "background", watch_background) + + async def on_idle(self, event: events.Idle) -> None: + if self.layout is None: + return + if self.layout.check_update(): + self.layout.reset_update() + await self.refresh_layout() + + async def _on_mouse_move(self, event: events.MouseMove) -> None: + + try: + if self.app.mouse_captured: + widget = self.app.mouse_captured + region = self.get_widget_region(widget) + else: + widget, region = self.get_widget_at(event.x, event.y) + except NoWidget: + await self.app.set_mouse_over(None) + else: + await self.app.set_mouse_over(widget) + await widget.forward_event( + events.MouseMove( + self, + event.x - region.x, + event.y - region.y, + event.delta_x, + event.delta_y, + event.button, + event.shift, + event.meta, + event.ctrl, + screen_x=event.screen_x, + screen_y=event.screen_y, + style=event.style, + ) + ) + + async def forward_event(self, event: events.Event) -> None: + event.set_forwarded() + if isinstance(event, (events.Enter, events.Leave)): + await self.post_message(event) + + elif isinstance(event, events.MouseMove): + event.style = self.get_style_at(event.screen_x, event.screen_y) + await self._on_mouse_move(event) + + elif isinstance(event, events.MouseEvent): + try: + if self.app.mouse_captured: + widget = self.app.mouse_captured + region = self.get_widget_region(widget) + else: + widget, region = self.get_widget_at(event.x, event.y) + except NoWidget: + pass + else: + 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) + await widget.forward_event(event.offset(-region.x, -region.y)) + + elif isinstance(event, (events.MouseScrollDown, events.MouseScrollUp)): + try: + widget, _region = self.get_widget_at(event.x, event.y) + except NoWidget: + return + scroll_widget = widget + if scroll_widget is not None: + await scroll_widget.forward_event(event) + else: + self.log("view.forwarded", event) + await self.post_message(event) + + async def action_toggle(self, name: str) -> None: + widget = self[name] + widget.visible = not widget.display + await self.post_message(messages.Layout(self)) diff --git a/src/textual/widget.py b/src/textual/widget.py index fbaf4fcc0..18b708ecd 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -16,6 +16,7 @@ import rich.repr from rich.align import Align from rich.console import Console, RenderableType 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 @@ -28,10 +29,11 @@ from ._callback import invoke from ._context import active_app from ._types import Lines from .dom import DOMNode -from .geometry import Size, Spacing +from .geometry import Offset, Size from .message import Message -from .messages import Layout, Update -from .reactive import watch +from . import messages +from .layout import Layout +from .reactive import Reactive, watch from .renderables.opacity import Opacity if TYPE_CHECKING: @@ -79,6 +81,10 @@ class Widget(DOMNode): super().__init__(name=name, id=id) + scroll_x = Reactive(0) + scroll_y = Reactive(0) + virtual_size = Reactive(Size(0, 0)) + def __init_subclass__(cls, can_focus: bool = True) -> None: super().__init_subclass__() cls.can_focus = can_focus @@ -145,6 +151,10 @@ class Widget(DOMNode): def size(self) -> Size: return self._size + @property + def scroll(self) -> Offset: + return Offset(self.scroll_x, self.scroll_y) + @property def is_visual(self) -> bool: return True @@ -166,6 +176,10 @@ class Widget(DOMNode): assert self._animate is not None return self._animate + @property + def layout(self) -> Layout | None: + return self.styles.layout + def on_style_change(self) -> None: self.clear_render_cache() @@ -237,7 +251,11 @@ class Widget(DOMNode): Returns: RenderableType: Any renderable """ - return Align.center(Text(f"#{self.id}"), vertical="middle") + + # Default displays a pretty repr in the center of the screen + return Align.center( + Pretty(self, no_wrap=True, overflow="ellipsis"), vertical="middle" + ) async def action(self, action: str, *params) -> None: await self.app.action(action, self) @@ -258,11 +276,11 @@ class Widget(DOMNode): # self.render_cache = None self.reset_check_repaint() self.reset_check_layout() - await self.emit(Layout(self)) + await self.emit(messages.Layout(self)) elif repaint or self.check_repaint(): # self.render_cache = None self.reset_check_repaint() - await self.emit(Update(self, self)) + await self.emit(messages.Update(self, self)) async def focus(self) -> None: await self.app.set_focus(self)