diff --git a/README.md b/README.md
index a34c1d27..265567e7 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ PySUS is a Python package for accessing and analyzing Brazil's public health dat
## What's New in PySUS 2.0
- **Simplified API**: New high-level functions for direct DataFrame access
-- **CLI & TUI**: Launch the text-based user interface from command line
+- **Streamlit Web UI**: Launch a local web interface for browsing and downloading datasets
- **Flexible Schema Modes**: Read multiple parquet files with union, intersection, or strict modes
- **SQL Query**: Filter catalog queries by dataset, group, state, year, and month
@@ -20,9 +20,9 @@ PySUS is a Python package for accessing and analyzing Brazil's public health dat
pip install pysus
```
-For the terminal user interface (TUI):
+For the local Streamlit web interface:
```bash
-pip install pysus[tui]
+pip install pysus[http]
```
### Docker
@@ -123,20 +123,24 @@ async def main():
df = pysus.read_parquet(paths, mode="union").df()
```
-### Using the TUI (unstable/under testing)
+### Using the Streamlit Web UI
-Launch the interactive text-based interface:
+Launch the local web interface:
```bash
-pysus tui -l pt
+pysus http
```
-Or from Python:
+Or with a custom port:
-```python
-from pysus.tui.app import PySUS
-app = PySUS(lang="pt")
-app.run()
+```bash
+pysus http -p 8080
+```
+
+Or run directly with Streamlit:
+
+```bash
+streamlit run pysus/http/app.py
```
## Features
@@ -146,7 +150,7 @@ app.run()
- **DuckLake Integration**: S3-compatible cloud storage for parquet catalogs
- **Local Catalog**: SQLite-based tracking of download history to avoid re-downloads
- **Type Inference**: Automatic data type conversion from legacy formats (DBF, DBC)
-- **CLI with TUI**: Command-line interface with interactive text-based UI
+- **CLI with Streamlit UI**: Command-line interface with local web-based UI
## Architecture
diff --git a/poetry.lock b/poetry.lock
index a568981f..bf59fe7f 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1734,10 +1734,9 @@ zstd = ["zstandard (>=0.18.0)"]
name = "humanize"
version = "4.15.0"
description = "Python humanize utilities"
-optional = true
+optional = false
python-versions = ">=3.10"
groups = ["main"]
-markers = "extra == \"tui\""
files = [
{file = "humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769"},
{file = "humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10"},
@@ -2416,28 +2415,6 @@ interegular = ["interegular (>=0.3.1,<0.4.0)"]
nearley = ["js2py"]
regex = ["regex"]
-[[package]]
-name = "linkify-it-py"
-version = "2.1.0"
-description = "Links recognition library with FULL unicode support."
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "linkify_it_py-2.1.0-py3-none-any.whl", hash = "sha256:0d252c1594ecba2ecedc444053db5d3a9b7ec1b0dd929c8f1d74dce89f86c05e"},
- {file = "linkify_it_py-2.1.0.tar.gz", hash = "sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b"},
-]
-
-[package.dependencies]
-uc-micro-py = "*"
-
-[package.extras]
-benchmark = ["pytest", "pytest-benchmark"]
-dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"]
-doc = ["myst-parser", "sphinx", "sphinx_book_theme"]
-test = ["coverage", "pytest", "pytest-cov"]
-
[[package]]
name = "loguru"
version = "0.6.0"
@@ -2470,7 +2447,6 @@ files = [
]
[package.dependencies]
-linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""}
mdurl = ">=0.1,<1.0"
[package.extras]
@@ -2690,27 +2666,6 @@ files = [
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
-[[package]]
-name = "mdit-py-plugins"
-version = "0.6.1"
-description = "Collection of plugins for markdown-it-py"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d"},
- {file = "mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0"},
-]
-
-[package.dependencies]
-markdown-it-py = ">=2.0.0,<5.0.0"
-
-[package.extras]
-code-style = ["pre-commit"]
-rtd = ["myst-parser", "sphinx-book-theme"]
-testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "pytest-timeout"]
-
[[package]]
name = "mdurl"
version = "0.1.2"
@@ -3360,12 +3315,11 @@ version = "4.9.6"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.10"
-groups = ["main", "dev", "docs"]
+groups = ["dev", "docs"]
files = [
{file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"},
{file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"},
]
-markers = {main = "extra == \"tui\""}
[[package]]
name = "pluggy"
@@ -5025,46 +4979,6 @@ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"]
typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"]
-[[package]]
-name = "textual"
-version = "8.2.7"
-description = "Modern Text User Interface framework"
-optional = true
-python-versions = "<4.0,>=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "textual-8.2.7-py3-none-any.whl", hash = "sha256:4caaa13a90bc4cf9c6c862c067ccd34fe84e9c161710a2a907a8026313b6bd73"},
- {file = "textual-8.2.7.tar.gz", hash = "sha256:658f568ff81e30ed43890c3e07520390e5cf1b4763822006e060656b0a88f105"},
-]
-
-[package.dependencies]
-markdown-it-py = {version = ">=2.1.0", extras = ["linkify"]}
-mdit-py-plugins = "*"
-platformdirs = ">=3.6.0,<5"
-pygments = ">=2.19.2,<3.0.0"
-rich = ">=14.2.0"
-tree-sitter = {version = ">=0.25.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-bash = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-css = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-go = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-html = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-java = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-javascript = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-json = {version = ">=0.24.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-markdown = {version = ">=0.3.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-python = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-regex = {version = ">=0.24.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-rust = {version = ">=0.23.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-sql = {version = ">=0.3.11", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-toml = {version = ">=0.6.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-xml = {version = ">=0.7.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-tree-sitter-yaml = {version = ">=0.6.0", optional = true, markers = "python_version >= \"3.10\" and extra == \"syntax\""}
-typing-extensions = ">=4.4.0,<5.0.0"
-
-[package.extras]
-syntax = ["tree-sitter (>=0.25.0) ; python_version >= \"3.10\"", "tree-sitter-bash (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-css (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-go (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-html (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-java (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-javascript (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-json (>=0.24.0) ; python_version >= \"3.10\"", "tree-sitter-markdown (>=0.3.0) ; python_version >= \"3.10\"", "tree-sitter-python (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-regex (>=0.24.0) ; python_version >= \"3.10\"", "tree-sitter-rust (>=0.23.0) ; python_version >= \"3.10\"", "tree-sitter-sql (>=0.3.11) ; python_version >= \"3.10\"", "tree-sitter-toml (>=0.6.0) ; python_version >= \"3.10\"", "tree-sitter-xml (>=0.7.0) ; python_version >= \"3.10\"", "tree-sitter-yaml (>=0.6.0) ; python_version >= \"3.10\""]
-
[[package]]
name = "tinycss2"
version = "1.4.0"
@@ -5200,397 +5114,6 @@ files = [
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "mypy (>=1.7.0,<1.19) ; platform_python_implementation == \"PyPy\"", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
-[[package]]
-name = "tree-sitter"
-version = "0.25.2"
-description = "Python bindings to the Tree-sitter parsing library"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20"},
- {file = "tree_sitter-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72a510931c3c25f134aac2daf4eb4feca99ffe37a35896d7150e50ac3eee06c7"},
- {file = "tree_sitter-0.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44488e0e78146f87baaa009736886516779253d6d6bac3ef636ede72bc6a8234"},
- {file = "tree_sitter-0.25.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2f8e7d6b2f8489d4a9885e3adcaef4bc5ff0a275acd990f120e29c4ab3395c5"},
- {file = "tree_sitter-0.25.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b570690f87f1da424cd690e51cc56728d21d63f4abd4b326d382a30353acc7"},
- {file = "tree_sitter-0.25.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0ec41b895da717bc218a42a3a7a0bfcfe9a213d7afaa4255353901e0e21f696"},
- {file = "tree_sitter-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:7712335855b2307a21ae86efe949c76be36c6068d76df34faa27ce9ee40ff444"},
- {file = "tree_sitter-0.25.2-cp310-cp310-win_arm64.whl", hash = "sha256:a925364eb7fbb9cdce55a9868f7525a1905af512a559303bd54ef468fd88cb37"},
- {file = "tree_sitter-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ca72d841215b6573ed0655b3a5cd1133f9b69a6fa561aecad40dca9029d75b"},
- {file = "tree_sitter-0.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc0351cfe5022cec5a77645f647f92a936b38850346ed3f6d6babfbeeeca4d26"},
- {file = "tree_sitter-0.25.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1799609636c0193e16c38f366bda5af15b1ce476df79ddaae7dd274df9e44266"},
- {file = "tree_sitter-0.25.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e65ae456ad0d210ee71a89ee112ac7e72e6c2e5aac1b95846ecc7afa68a194c"},
- {file = "tree_sitter-0.25.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:49ee3c348caa459244ec437ccc7ff3831f35977d143f65311572b8ba0a5f265f"},
- {file = "tree_sitter-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:56ac6602c7d09c2c507c55e58dc7026b8988e0475bd0002f8a386cce5e8e8adc"},
- {file = "tree_sitter-0.25.2-cp311-cp311-win_arm64.whl", hash = "sha256:b3d11a3a3ac89bb8a2543d75597f905a9926f9c806f40fcca8242922d1cc6ad5"},
- {file = "tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960"},
- {file = "tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c"},
- {file = "tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99"},
- {file = "tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9"},
- {file = "tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac"},
- {file = "tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897"},
- {file = "tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5"},
- {file = "tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd"},
- {file = "tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601"},
- {file = "tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053"},
- {file = "tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614"},
- {file = "tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae"},
- {file = "tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b"},
- {file = "tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8"},
- {file = "tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0"},
- {file = "tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87"},
- {file = "tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab"},
- {file = "tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358"},
- {file = "tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0"},
- {file = "tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721"},
- {file = "tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f"},
-]
-
-[package.extras]
-docs = ["sphinx (>=8.1,<9.0)", "sphinx-book-theme"]
-tests = ["tree-sitter-html (>=0.23.2)", "tree-sitter-javascript (>=0.23.1)", "tree-sitter-json (>=0.24.8)", "tree-sitter-python (>=0.23.6)", "tree-sitter-rust (>=0.23.2)"]
-
-[[package]]
-name = "tree-sitter-bash"
-version = "0.25.1"
-description = "Bash grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0e6235f59e366d220dde7d830196bed597d01e853e44d8ccd1a82c5dd2500acf"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f4a34a6504c7c5b2a9b8c5c4065531dea19ca2c35026e706cf2eeeebe2c92512"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e76c4cfb20b076552406782b7f8c2a3946835993df0a44df006de54b7030c7dc"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f484c4bb8796cde7a87ca351e6116f09653edac0eb3c6d238566359dd28b117"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5e76af6df46d958c7f5b6d5884c9743218e3902a00ccb493ec92728b1084430b"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a3332d71c7b7d5f78259b19d02d0ea111fcb82b72712ee4a93aaa5b226d3f0a8"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:52a6802d9218f86278aa3e8b459c3abdad67eed0fde1f9f13aca5b6c634217a6"},
- {file = "tree_sitter_bash-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:59115057ec2bae319e8082ff29559861045002964c3431ccb0fc92aa4bc9bccb"},
- {file = "tree_sitter_bash-0.25.1.tar.gz", hash = "sha256:bfc0bdaa77bc1e86e3c6652e5a6e140c40c0a16b84185c2b63ad7cd809b88f14"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-css"
-version = "0.25.0"
-description = "CSS grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_css-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ddce6f84eeb0bb2877b4587b07bffb0753040c44d811ed9ab2af978c313beda8"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:5a2a9c875037ef5f9da57697fb8075086476d42a49d25a88dcca60dfc09bd092"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4f5e1135bfd01bce24e2fc7bca1381f52bdd6c6282ee28f7aa77185340bcd135"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b6d0084536828c733a66524a43c9df89f335971d5b1b973e9d1c42ba9dd426b"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8a83825daf538656cb88f4f7a0dd9963e3f204e83e7f8d92131f17e5bd712a77"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b486c097d250a598fba5f1f46f62697c7f4428252c8bdaad696a907ee913421d"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:fe319e4ad1b8327afbd9758b3ae22b09226d6c28dc9b022bcadabdaf6ea3716c"},
- {file = "tree_sitter_css-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:4fc2c82645cd593f1c695b4d6b678d71e633212ca030f26dedee4f92434bfe21"},
- {file = "tree_sitter_css-0.25.0.tar.gz", hash = "sha256:2fc996bf05b04e06061e88ee4c60837783dc4e62a695205acbc262ee30454138"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-go"
-version = "0.25.0"
-description = "Go grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_go-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b852993063a3429a443e7bd0aa376dd7dd329d595819fabf56ac4cf9d7257b54"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:503b81a2b4c31e302869a1de3a352ad0912ccab3df9ac9950197b0a9ceeabd8f"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04b3b3cb4aff18e74e28d49b716c6f24cb71ddfdd66768987e26e4d0fa812f74"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:148255aca2f54b90d48c48a9dbb4c7faad6cad310a980b2c5a5a9822057ed145"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4d338116cdf8a6c6ff990d2441929b41323ef17c710407abe0993c13417d6aad"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5608e089d2a29fa8d2b327abeb2ad1cdb8e223c440a6b0ceab0d3fa80bdeebae"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:30d4ada57a223dfc2c32d942f44d284d40f3d1215ddcf108f96807fd36d53022"},
- {file = "tree_sitter_go-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5d62362059bf79997340773d47cc7e7e002883b527a05cca829c46e40b70ded"},
- {file = "tree_sitter_go-0.25.0.tar.gz", hash = "sha256:a7466e9b8d94dda94cae8d91629f26edb2d26166fd454d4831c3bf6dfa2e8d68"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-html"
-version = "0.23.2"
-description = "HTML grammar for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_html-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e1641d5edf5568a246c6c47b947ed524b5bf944664e6473b21d4ae568e28ee9"},
- {file = "tree_sitter_html-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:3d0a83dd6cd1c7d4bcf6287b5145c92140f0194f8516f329ae8b9e952fbfa8ff"},
- {file = "tree_sitter_html-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b3775732fffc0abd275a419ef018fd4c1ad4044b2a2e422f3378d93c30eded"},
- {file = "tree_sitter_html-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bdaa7ac5030d416aea0c512d4810ef847bbbd62d61e3d213f370b64ce147293"},
- {file = "tree_sitter_html-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2e9631b66041a4fd792d7f79a0c4128adb3bfc71f3dcb7e1a3eab5dbee77d67"},
- {file = "tree_sitter_html-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:85095f49f9e57f0ac9087a3e830783352c8447fdda55b1c1139aa47e5eaa0e21"},
- {file = "tree_sitter_html-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:0f65ed9e877144d0f04ade5644e5b0e88bf98a9e60bce65235c99905623e2f1a"},
- {file = "tree_sitter_html-0.23.2.tar.gz", hash = "sha256:bc9922defe23144d9146bc1509fcd00d361bf6b3303f9effee6532c6a0296961"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.22,<1.0)"]
-
-[[package]]
-name = "tree-sitter-java"
-version = "0.23.5"
-description = "Java grammar for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df"},
- {file = "tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69"},
- {file = "tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9401e7271f0b333df39fc8a8336a0caf1b891d9a2b89ddee99fae66b794fc5b7"},
- {file = "tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370b204b9500b847f6d0c5ad584045831cee69e9a3e4d878535d39e4a7e4c4f1"},
- {file = "tree_sitter_java-0.23.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aae84449e330363b55b14a2af0585e4e0dae75eb64ea509b7e5b0e1de536846a"},
- {file = "tree_sitter_java-0.23.5-cp39-abi3-win_amd64.whl", hash = "sha256:1ee45e790f8d31d416bc84a09dac2e2c6bc343e89b8a2e1d550513498eedfde7"},
- {file = "tree_sitter_java-0.23.5-cp39-abi3-win_arm64.whl", hash = "sha256:402efe136104c5603b429dc26c7e75ae14faaca54cfd319ecc41c8f2534750f4"},
- {file = "tree_sitter_java-0.23.5.tar.gz", hash = "sha256:f5cd57b8f1270a7f0438878750d02ccc79421d45cca65ff284f1527e9ef02e38"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.22,<1.0)"]
-
-[[package]]
-name = "tree-sitter-javascript"
-version = "0.25.0"
-description = "JavaScript grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c"},
- {file = "tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b"},
- {file = "tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-json"
-version = "0.24.8"
-description = "JSON grammar for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:59ac06c6db1877d0e2076bce54a5fddcdd2fc38ca778905662e80fa9ffcea2ab"},
- {file = "tree_sitter_json-0.24.8-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:62b4c45b561db31436a81a3f037f71ec29049f4fc9bf5269b6ec3ebaaa35a1cd"},
- {file = "tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8627f7d375fda9fc193ebee368c453f374f65c2f25c58b6fea4e6b49a7fccbc"},
- {file = "tree_sitter_json-0.24.8-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85cca779872f7278f3a74eb38533d34b9c4de4fd548615e3361fa64fe350ad0a"},
- {file = "tree_sitter_json-0.24.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:deeb45850dcc52990fbb52c80196492a099e3fa3512d928a390a91cf061068cc"},
- {file = "tree_sitter_json-0.24.8-cp39-abi3-win_amd64.whl", hash = "sha256:e4849a03cd7197267b2688a4506a90a13568a8e0e8588080bd0212fcb38974e3"},
- {file = "tree_sitter_json-0.24.8-cp39-abi3-win_arm64.whl", hash = "sha256:591e0096c882d12668b88f30d3ca6f85b9db3406910eaaab6afb6b17d65367dd"},
- {file = "tree_sitter_json-0.24.8.tar.gz", hash = "sha256:ca8486e52e2d261819311d35cf98656123d59008c3b7dcf91e61d2c0c6f3120e"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.22,<1.0)"]
-
-[[package]]
-name = "tree-sitter-markdown"
-version = "0.5.1"
-description = "Markdown grammar for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f00ce3f48f127377983859fcb93caf0693cbc7970f8c41f1e2bd21e4d56bdfd8"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1ec4cc5d7b0d188bad22247501ab13663bb1bf1a60c2c020a22877fabce8daa9"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727242a70c46222092eba86c102301646f21ba32aee221f4b1f70e2020755e81"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0b2fde19e692bb90e300d9788887528c624b659c794de6337f8193396de4399"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:13da82db04cec7910b6afd4a67d02da9ef402df8d56fc6ed85e00584af1730ee"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8a8a04a5d942c177cc590ec40074fcf3658f3a7c0a3388a8575990003665d8c"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:b1b0e4cbcf5a7b85005f1e9266fc2ed9b649b41a6048f3b1abae3612368d97a6"},
- {file = "tree_sitter_markdown-0.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:2296ef53a757d8f5b848616706d0518e04d487bc7748bd05755d4a3a65711542"},
- {file = "tree_sitter_markdown-0.5.1.tar.gz", hash = "sha256:6c69d7270a7e09be8988ced44584c09a6a4f541cea0dc394dd1c1a5ac3b5601d"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.23,<1.0)"]
-
-[[package]]
-name = "tree-sitter-python"
-version = "0.25.0"
-description = "Python grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76"},
- {file = "tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb"},
- {file = "tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-regex"
-version = "0.25.0"
-description = "Regex grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3fa11bbd76b29ac8ca2dbf85ad082f9b18ae6352251d805eb2d4191e1706a9d5"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:df5713649b89c5758649398053c306c41565f22a6f267cb5ec25596504bcf012"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cdd92400fd9d8229e584c55e12410251561f0d47eea49db17805e2f64a8b2490"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cceab1c14deeec9c5899babcb2b7942f0607b4355e66eab4083514f644f1bd52"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:253436be178150ca4a0603720e0c246e08b5bdd2dc6df313667d97e6c0fce846"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:883eacc46fd7eaffc328efd5865f1fe8825711892d3a89fccc2c414b061e806d"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:f0f2ebf9a6bb5d0d0da2a8ac51d7e5a985b87cdb24d86db5ddc6a58baf115d5d"},
- {file = "tree_sitter_regex-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:d5a36150daa452f8aec1c2d6d1f2d26255dc05d1490f9618b14c12a6a648cda4"},
- {file = "tree_sitter_regex-0.25.0.tar.gz", hash = "sha256:5d29111b3f27d4afb31496476d392d1f562fe0bfe954e8968f1d8683424fc331"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-rust"
-version = "0.24.2"
-description = "Rust grammar for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3620cfd12340efa43082d45df76349ff511893a9c361da2f8d6d51e307020a59"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01a46622735498493f29f3e628a90de95c96a07bfbeb88996243eb986b1cee36"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e033c5a93b57c88e0a835880de39fc802909ff69f57aaff6000211c196ea5190"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d76d1208c3638b871236090759dfc13d478921320653a6c9da5336e7c58f65a"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87930163a462408c49ab62c667e74029bc26b4cc7123dd1bdc7352215786c64a"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da2b86099028fd42c6cd32878b7b16b01f8aac0f7b0e98742b7fa6bc3cf09b89"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-win_amd64.whl", hash = "sha256:4529c125d928882ddfb879fdc6bc0704913261ecc078b6fa7902559e0daf200d"},
- {file = "tree_sitter_rust-0.24.2-cp39-abi3-win_arm64.whl", hash = "sha256:66ba90f61bd54f4c4f5d30434957daf64507c16b0313df76becb37d63f70a227"},
- {file = "tree_sitter_rust-0.24.2.tar.gz", hash = "sha256:54fb02a5911e345308b405174465112479f56dc39e3f1e7744d7568595f00db9"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.22,<1.0)"]
-
-[[package]]
-name = "tree-sitter-sql"
-version = "0.3.11"
-description = "Tree-sitter Grammar for SQL"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cf1b0c401756940bf47544ad7c4cc97373fc0dac118f821820953e7015a115e3"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:a33cd6880ab2debef036f80365c32becb740ec79946805598488732b6c515fff"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:344e99b59c8c8d72f7154041e9d054400f4a3fccc16c2c96ac106dde0e7f8d0c"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5128b12f71ac0f5ebcc607f67a62cdc56a187c1a5ba7553feeb9c5f6f9bc3c72"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:03cc164fcf7b1f711e7d939aeb4d1f62c76f4162e081c70b860b4fcd91806a38"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0e22ea8de690dd9960d8c0c36c4cd25417b084e1e29c91ac0235fbdb3abb4664"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-win_amd64.whl", hash = "sha256:c57b877702d218c0856592d33320c02b2dc8411d8820b3bf7b81be86c54fa0bb"},
- {file = "tree_sitter_sql-0.3.11-cp310-abi3-win_arm64.whl", hash = "sha256:8a1e42f0a2c9b01b23074708ecf5b8d21b9a0440e3dff279d8cf466cdf1a877e"},
- {file = "tree_sitter_sql-0.3.11.tar.gz", hash = "sha256:700b93be2174c3c83d174ec3e10b682f72a4fb451f0076c7ce5012f1d5a76cbc"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
-[[package]]
-name = "tree-sitter-toml"
-version = "0.7.0"
-description = "TOML grammar for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b9ae5c3e7c5b6bb05299dd73452ceafa7fa0687d5af3012332afa7757653b676"},
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:18be09538e9775cddc0290392c4e2739de2201260af361473ca60b5c21f7bd22"},
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a045e0acfcf91b7065066f7e51ea038ed7385c1e35e7e8fae18f252d3f8adb8c"},
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a2f8cf9d73f07b6628093b35e5c5fbac039247e32cb075eaa5289a5914e73af"},
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:860ffa4513b2dc3083d8e412bd815a350b0a9490624b37e7c8f6ed5c6f9ce63c"},
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:2760a04f06937b01b1562a2135cd7e8207e399e73ef75bbebc77e37b1ad3b15d"},
- {file = "tree_sitter_toml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fd00fd8a51c65aa19c40539431cb1773d87c30af5757b4041fa6c229058420b4"},
- {file = "tree_sitter_toml-0.7.0.tar.gz", hash = "sha256:29e257612fa8f0c1fcbc4e7e08ddc561169f1725265302e64d81086354144a70"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.22,<1.0)"]
-
-[[package]]
-name = "tree-sitter-xml"
-version = "0.7.0"
-description = "XML & DTD grammars for tree-sitter"
-optional = true
-python-versions = ">=3.9"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc3e516d4c1e0860fb22172c172148debb825ba638971bc48bad15b22e5b0bae"},
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:0674fdf4cc386e4d323cb287d3b072663de0f20a9e9af5d5e09821aae56a9e5c"},
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c0fe5f2d6cc09974c8375c8ea9b24909f493b5bf04aacdc4c694b5d2ae6b040"},
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3209516a4d84dff90bc91d2ad2ce246de8504cede4358849687fa8e71536e7"},
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:87578e15fa55f44ecd9f331233b6f8a2cbde3546b354c830ecb862a632379455"},
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-win_amd64.whl", hash = "sha256:9ba2dafc6ce9feaf4ccc617d3aeea57f8e0ca05edad34953e788001ebff79133"},
- {file = "tree_sitter_xml-0.7.0-cp39-abi3-win_arm64.whl", hash = "sha256:fc759f710a8fd7a01c23e2d7cb013679199045bea3dc0e5151650a11322aaf40"},
- {file = "tree_sitter_xml-0.7.0.tar.gz", hash = "sha256:ab0ff396f20230ad8483d968151ce0c35abe193eb023b20fbd8b8ce4cf9e9f61"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.22,<1.0)"]
-
-[[package]]
-name = "tree-sitter-yaml"
-version = "0.7.2"
-description = "YAML grammar for tree-sitter"
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7e269ddcfcab8edb14fbb1f1d34eed1e1e26888f78f94eedfe7cc98c60f8bc9f"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:0807b7966e23ddf7dddc4545216e28b5a58cdadedcecca86b8d8c74271a07870"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1a5c60c98b6c4c037aae023569f020d0c489fad8dc26fdfd5510363c9c29a41"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88636d19d0654fd24f4f242eaaafa90f6f5ebdba8a62e4b32d251ed156c51a2a"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1d2e8f0bb14aa4537320952d0f9607eef3021d5aada8383c34ebeece17db1e06"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:74ca712c50fc9d7dbc68cb36b4a7811d6e67a5466b5a789f19bf8dd6084ef752"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-win_amd64.whl", hash = "sha256:7587b5ca00fc4f9a548eff649697a3b395370b2304b399ceefa2087d8a6c9186"},
- {file = "tree_sitter_yaml-0.7.2-cp310-abi3-win_arm64.whl", hash = "sha256:f63c227b18e7ce7587bce124578f0bbf1f890ac63d3e3cd027417574273642c4"},
- {file = "tree_sitter_yaml-0.7.2.tar.gz", hash = "sha256:756db4c09c9d9e97c81699e8f941cb8ce4e51104927f6090eefe638ee567d32c"},
-]
-
-[package.extras]
-core = ["tree-sitter (>=0.24,<1.0)"]
-
[[package]]
name = "typeguard"
version = "4.5.2"
@@ -5681,22 +5204,6 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
-[[package]]
-name = "uc-micro-py"
-version = "2.0.0"
-description = "Micro subset of unicode data files for linkify-it-py projects."
-optional = true
-python-versions = ">=3.10"
-groups = ["main"]
-markers = "extra == \"tui\""
-files = [
- {file = "uc_micro_py-2.0.0-py3-none-any.whl", hash = "sha256:3603a3859af53e5a39bc7677713c78ea6589ff188d70f4fee165db88e22b242c"},
- {file = "uc_micro_py-2.0.0.tar.gz", hash = "sha256:c53691e495c8db60e16ffc4861a35469b0ba0821fe409a8a7a0a71864d33a811"},
-]
-
-[package.extras]
-test = ["coverage", "pytest", "pytest-cov"]
-
[[package]]
name = "unidecode"
version = "1.4.0"
@@ -5841,9 +5348,9 @@ files = [
dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
[extras]
-tui = ["humanize", "textual"]
+http = []
[metadata]
lock-version = "2.1"
python-versions = ">=3.11,<3.14"
-content-hash = "d1f063bac8e016d4ccc3dae4ddca6d126d654f598d8ced0ead4541d36b5fc287"
+content-hash = "2fdd6705ff86f8c17e9ab4a869440f57910087935a3c6a434d9b7ff9e2398208"
diff --git a/pyproject.toml b/pyproject.toml
index 8167e588..a2ab1121 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,12 +43,10 @@ pyreaddbc = ">=2.0.4"
dotenv = "^0.9.9"
boto3 = "^1.42.89"
typer = "^0.24.1"
-
-humanize = { version = "^4.8.0", optional = true }
-textual = { extras = ["syntax"], version = "^8.2.1", optional = true }
+humanize = "^4.8.0"
[tool.poetry.extras]
-tui = ["textual", "humanize"]
+http = ["streamlit"]
[tool.poetry.group.dev.dependencies]
pytest = ">=6.1.0"
diff --git a/pysus/cli/__init__.py b/pysus/cli/__init__.py
index 69b2381d..427741e8 100644
--- a/pysus/cli/__init__.py
+++ b/pysus/cli/__init__.py
@@ -5,28 +5,52 @@
@app.command()
-def tui(
- lang: str = typer.Option( # noqa
- "en",
- "-l",
- "--lang",
- help="Language (en, pt)",
+def version():
+ print(__version__)
+
+
+@app.command()
+def http(
+ port: int = typer.Option(
+ 8501,
+ "-p",
+ "--port",
+ help="Port to bind the server to",
),
):
+ """Launch the local Streamlit visual interface."""
try:
- from pysus.tui.app import PySUS
+ import streamlit.web.bootstrap as bootstrap
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
except ImportError:
raise ImportError(
- "The TUI requires extra dependencies. "
- "Install them with: pip install pysus[tui]"
+ "The HTTP UI requires extra dependencies. "
+ "Install them with: pip install pysus[http]"
)
- app = PySUS(lang=lang)
- app.run()
+ import os
+ import sys
+ import webbrowser
+ app_path = os.path.join(os.path.dirname(__file__), "..", "http", "app.py")
+ app_path = os.path.abspath(app_path)
-@app.command()
-def version():
- print(__version__)
+ from streamlit.web import cli as stcli
+
+ sys.argv = [
+ "streamlit",
+ "run",
+ app_path,
+ "--server.port",
+ str(port),
+ "--server.headless",
+ "true",
+ "--server.address",
+ "localhost",
+ ]
+
+ webbrowser.open(f"http://localhost:{port}")
+
+ stcli.main()
if __name__ == "__main__":
diff --git a/pysus/http/__init__.py b/pysus/http/__init__.py
new file mode 100644
index 00000000..c16bcf3f
--- /dev/null
+++ b/pysus/http/__init__.py
@@ -0,0 +1,3 @@
+"""PySUS HTTP — Streamlit-based visual interface for localhost use."""
+
+from pysus.http.app import home # noqa
diff --git a/pysus/http/app.py b/pysus/http/app.py
new file mode 100644
index 00000000..d547a220
--- /dev/null
+++ b/pysus/http/app.py
@@ -0,0 +1,90 @@
+"""PySUS Streamlit App — localhost visual interface for the PySUS package.
+
+Run with:
+ streamlit run pysus/http/app.py
+ or
+ pysus http
+"""
+
+import asyncio
+
+import streamlit as st
+
+from pysus import __version__
+from pysus.http.translations import t
+from pysus.api.client import PySUS
+
+LANGUAGES = {"English": "en", "Português": "pt"}
+LANG_LABELS = {v: k for k, v in LANGUAGES.items()}
+
+st.set_page_config(
+ page_title="PySUS",
+ page_icon=":hospital:",
+ layout="wide",
+)
+
+st.markdown(
+ """
+
+ """,
+ unsafe_allow_html=True,
+)
+
+
+@st.cache_data(show_spinner="Loading datasets...")
+def load_catalog() -> None:
+ async def _fetch():
+ async with PySUS():
+ return
+
+ return asyncio.run(_fetch())
+
+
+def _init_lang() -> None:
+ if "lang" not in st.session_state:
+ st.session_state.lang = "en"
+
+
+def _on_lang_change() -> None:
+ label = st.session_state.get("_lang_select", "English")
+ st.session_state.lang = LANGUAGES[label]
+
+
+def _lang_selector() -> None:
+ _init_lang()
+ current_label = LANG_LABELS.get(st.session_state.lang, "English")
+ st.sidebar.selectbox(
+ t("lang_label", st.session_state.lang),
+ list(LANGUAGES.keys()),
+ index=list(LANGUAGES.keys()).index(current_label),
+ key="_lang_select",
+ on_change=_on_lang_change,
+ )
+
+
+def home() -> None:
+ catalog = load_catalog()
+ st.session_state.catalog = catalog
+
+ _lang_selector()
+ lang: str = st.session_state.lang
+
+
+if __name__ == "__main__":
+ _init_lang()
+ lang = st.session_state.lang
+
+ home_page = st.Page(home, title=t("home_page", lang), default=True)
+ datasets_page = st.Page("pages/1_ducklake.py", title="Datasets")
+ ftp_page = st.Page("pages/2_ftp.py", title="FTP")
+ dadosgov_page = st.Page("pages/3_dadosgov.py", title="DadosGov")
+
+ st.logo(
+ "https://raw.githubusercontent.com/luabida/PySUS/709a96d0cc9199894a2d9619a0189617d8f46a55/pysus/http/assets/logo_large.svg",
+ icon_image="https://raw.githubusercontent.com/luabida/PySUS/7a3c210c80c47362d70996c0a005b60321f4bffa/pysus/http/assets/logo.svg",
+ )
+
+ pg = st.navigation([home_page, datasets_page, ftp_page, dadosgov_page])
+ pg.run()
diff --git a/pysus/http/assets/logo.svg b/pysus/http/assets/logo.svg
new file mode 100644
index 00000000..e412028a
--- /dev/null
+++ b/pysus/http/assets/logo.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/pysus/http/assets/logo_large.svg b/pysus/http/assets/logo_large.svg
new file mode 100644
index 00000000..3b87b0a0
--- /dev/null
+++ b/pysus/http/assets/logo_large.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/pysus/http/pages/1_ducklake.py b/pysus/http/pages/1_ducklake.py
new file mode 100644
index 00000000..86441752
--- /dev/null
+++ b/pysus/http/pages/1_ducklake.py
@@ -0,0 +1,3 @@
+import streamlit as st
+
+st.title("Datasets")
diff --git a/pysus/http/pages/2_ftp.py b/pysus/http/pages/2_ftp.py
new file mode 100644
index 00000000..92501d59
--- /dev/null
+++ b/pysus/http/pages/2_ftp.py
@@ -0,0 +1,3 @@
+import streamlit as st
+
+st.title("FTP Client")
diff --git a/pysus/http/pages/3_dadosgov.py b/pysus/http/pages/3_dadosgov.py
new file mode 100644
index 00000000..6a5b3263
--- /dev/null
+++ b/pysus/http/pages/3_dadosgov.py
@@ -0,0 +1,3 @@
+import streamlit as st
+
+st.title("DadosGov Client")
diff --git a/pysus/http/translations.py b/pysus/http/translations.py
new file mode 100644
index 00000000..aea791a2
--- /dev/null
+++ b/pysus/http/translations.py
@@ -0,0 +1,85 @@
+"""Translation dictionaries for the PySUS Streamlit UI."""
+
+from typing import Final
+
+EN: Final[dict[str, str]] = {
+ "lang_label": "Language",
+ "home_page": "Home",
+ "sidebar_title": "Datasets",
+ "sidebar_select": "Select a dataset",
+ "home_title": "PySUS",
+ "home_subtitle": "Tools for dealing with Brazil's Public health data (SUS — Sistema Único de Saúde).",
+ "coming_soon": "coming soon",
+ "datasets": "Datasets",
+ "data_sources": "Data sources",
+ "about_title": "About PySUS",
+ "about_intro": "PySUS v{version} — Tools for dealing with Brazil's Public health data (SUS — Sistema Único de Saúde).",
+ "sinan_desc": "Notifiable Diseases Information System",
+ "sinasc_desc": "Live Births Information System",
+ "sim_desc": "Mortality Information System",
+ "sih_desc": "Hospital Information System",
+ "sia_desc": "Ambulatory Information System",
+ "pni_desc": "National Immunization Program",
+ "ibge_desc": "Brazilian Institute of Geography and Statistics",
+ "cnes_desc": "National Registry of Health Facilities",
+ "ciha_desc": "Hospital Admission Communication",
+ "ducklake_desc": "Modern data lake backend (default).",
+ "ftp_desc": "Legacy FTP downloads from DATASUS.",
+ "dadosgov_desc": "Brazilian open-data portal (dados.gov.br).",
+ "browser_title": "Datasets",
+ "browser_choose": "Choose a dataset",
+ "browser_placeholder": "{dataset} browser — coming soon.",
+ "state": "State",
+ "year": "Year",
+ "month": "Month",
+ "group": "Group",
+ "fetch": "Fetch data",
+}
+
+PT: Final[dict[str, str]] = {
+ "lang_label": "Idioma",
+ "home_page": "Início",
+ "sidebar_title": "Bases de dados",
+ "sidebar_select": "Selecione uma base",
+ "home_title": "PySUS",
+ "home_subtitle": "Ferramentas para dados públicos de saúde do Brasil (SUS — Sistema Único de Saúde).",
+ "coming_soon": "em breve",
+ "datasets": "Bases de dados",
+ "data_sources": "Fontes de dados",
+ "about_title": "Sobre o PySUS",
+ "about_intro": "PySUS v{version} — Ferramentas para dados públicos de saúde do Brasil (SUS — Sistema Único de Saúde).",
+ "sinan_desc": "Sistema de Informação de Agravos de Notificação",
+ "sinasc_desc": "Sistema de Informações sobre Nascidos Vivos",
+ "sim_desc": "Sistema de Informação sobre Mortalidade",
+ "sih_desc": "Sistema de Informações Hospitalares",
+ "sia_desc": "Sistema de Informações Ambulatoriais",
+ "pni_desc": "Programa Nacional de Imunizações",
+ "ibge_desc": "Instituto Brasileiro de Geografia e Estatística",
+ "cnes_desc": "Cadastro Nacional de Estabelecimentos de Saúde",
+ "ciha_desc": "Comunicação de Internação Hospitalar e Ambulatorial",
+ "ducklake_desc": "Backend moderno de data lake (padrão).",
+ "ftp_desc": "Downloads legados via FTP do DATASUS.",
+ "dadosgov_desc": "Portal de dados abertos do governo (dados.gov.br).",
+ "browser_title": "Bases de dados",
+ "browser_choose": "Escolha uma base",
+ "browser_placeholder": "Navegador {dataset} — em breve.",
+ "state": "Estado",
+ "year": "Ano",
+ "month": "Mês",
+ "group": "Grupo",
+ "fetch": "Baixar dados",
+}
+
+TRANSLATIONS: Final[dict[str, dict[str, str]]] = {
+ "en": EN,
+ "pt": PT,
+}
+
+
+def t(
+ key: str, lang: str = "en", **kwargs: str
+) -> str:
+ text = TRANSLATIONS.get(lang, EN).get(key, key)
+ if kwargs:
+ text = text.format(**kwargs)
+ return text
diff --git a/pysus/tui/__init__.py b/pysus/tui/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/pysus/tui/app.py b/pysus/tui/app.py
deleted file mode 100644
index dacaa12a..00000000
--- a/pysus/tui/app.py
+++ /dev/null
@@ -1,251 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-
-from pysus import __version__
-from pysus.api import PySUSClient
-from pysus.api.client import DownloadStatus
-from pysus.tui.i18n import TRANSLATIONS, t
-from pysus.tui.screens import (
- ConfigScreen,
- InfoModal,
- LoadingScreen,
- MainScreen,
- SearchModal,
-)
-from textual import work
-from textual.app import App
-from textual.binding import Binding
-from textual.widgets import (
- ContentSwitcher,
- DataTable,
- ProgressBar,
- Static,
- Tree,
-)
-
-
-class PySUS(App):
- TITLE = "PySUS"
- SUB_TITLE = f"v{__version__}"
- CSS_PATH = "style.tcss"
-
- lang: str
- pysus: PySUSClient
-
- BINDINGS = [
- Binding("escape", "back", "Back"),
- Binding("q", "quit", "Quit"),
- Binding("f10", "push_screen('config')", "Config", priority=True),
- Binding("i", "show_info", "Info"),
- Binding("d", "download", "Download"),
- Binding("/", "search", "Search"),
- Binding("h", "focus_previous", "Focus Prev", show=False),
- Binding("l", "focus_next", "Focus Next", show=False),
- Binding("j", "cursor_down", "Down", show=False),
- Binding("k", "cursor_up", "Up", show=False),
- ]
-
- SCREENS = {
- "main": MainScreen,
- "config": ConfigScreen,
- }
-
- def __init__(self, lang="en", **kwargs):
- self.lang = lang if lang in TRANSLATIONS else "en"
- super().__init__(**kwargs)
-
- async def on_mount(self) -> None:
- self.pysus = PySUSClient()
- await self.push_screen(LoadingScreen())
- self.init()
-
- @work
- async def init(self) -> None:
- try:
- await self.pysus.__aenter__()
- await asyncio.sleep(2)
- self.switch_screen("main")
- except Exception as e: # noqa
- err_msg = t("loading_err", lang=self.lang)
- self.notify(f"{err_msg}: {e}", severity="error")
-
- @work
- async def action_download(self) -> None:
- screen = self.screen
- if not isinstance(screen, MainScreen):
- return
-
- switcher = screen.query_one("#client-switcher", ContentSwitcher)
- current_client = switcher.current
-
- if current_client == "ducklake":
- table = screen.query_one("#ducklake", DataTable)
- manager = screen.ducklake_manager
- elif current_client == "ftp":
- table = screen.query_one("#ftp", DataTable)
- manager = screen.ftp_manager
- else:
- table = screen.query_one("#dadosgov", DataTable)
- manager = screen.dadosgov_manager
-
- if table.cursor_row is None:
- return
-
- saved_row_index = table.cursor_row
- selected_wrapper = manager.filtered[saved_row_index]
- file_item = selected_wrapper.raw
-
- progress_bar = screen.query_one(
- "#download-progress",
- ProgressBar,
- )
- progress_bar.add_class("visible")
-
- download_text = screen.query_one("#download-text", Static)
- download_text.update(f"Downloading: {file_item.name}")
- download_text.add_class("visible")
-
- def run_download():
- import anyio
-
- async def do_download():
- return await self.pysus.download_to_parquet(file_item)
-
- return anyio.run(do_download)
-
- await asyncio.get_event_loop().run_in_executor(None, run_download)
-
- progress_bar.remove_class("visible")
- download_text.remove_class("visible")
- download_text.update("")
-
- completed_paths = self.pysus.get_completed_remote_paths()
- manager.set_items(
- [w.raw for w in manager.items],
- downloaded_paths=completed_paths,
- )
-
- if hasattr(manager, "search_text") and manager.search_text:
- manager.apply_filter(manager.search_text)
-
- manager.populate(table)
- self.populate_local_tree()
-
- @work
- async def refresh_local_tree(self) -> None:
- await asyncio.sleep(0.5)
- self.populate_local_tree()
-
- def action_back(self) -> None:
- if isinstance(self.screen, ConfigScreen):
- self.pop_screen()
-
- def action_search(self) -> None:
- screen = self.screen
- if not isinstance(screen, MainScreen):
- return
-
- def perform_search(val: str | None) -> None:
- switcher = screen.query_one("#client-switcher", ContentSwitcher)
- current_client = switcher.current
- if current_client == "ducklake":
- screen.ducklake_manager.apply_filter(val)
- screen.ducklake_manager.populate(
- screen.query_one("#ducklake", DataTable)
- )
- elif current_client == "ftp":
- screen.ftp_manager.apply_filter(val)
- screen.ftp_manager.populate(screen.query_one("#ftp", DataTable))
- else:
- screen.dadosgov_manager.apply_filter(val)
- screen.dadosgov_manager.populate(
- screen.query_one("#dadosgov", DataTable)
- )
-
- self.push_screen(SearchModal(), perform_search)
-
- def action_show_info(self) -> None:
- screen = self.screen
- if not isinstance(screen, MainScreen):
- return
-
- switcher = screen.query_one("#client-switcher", ContentSwitcher)
- current_client = switcher.current
-
- if current_client == "ducklake":
- table = screen.query_one("#ducklake", DataTable)
- manager = screen.ducklake_manager
- elif current_client == "ftp":
- table = screen.query_one("#ftp", DataTable)
- manager = screen.ftp_manager
- else:
- table = screen.query_one("#dadosgov", DataTable)
- manager = screen.dadosgov_manager
-
- try:
- if table.cursor_row is not None:
- selected_wrapper = manager.filtered[table.cursor_row]
- self.push_screen(InfoModal(selected_wrapper.raw))
- except Exception as e: # noqa
- self.notify(f"Metadata error: {e}", severity="error")
-
- def action_cursor_down(self) -> None:
- if isinstance(self.focused, (DataTable, Tree)):
- self.focused.action_cursor_down()
-
- def action_cursor_up(self) -> None:
- if isinstance(self.focused, (DataTable, Tree)):
- self.focused.action_cursor_up()
-
- async def action_quit(self) -> None:
- await self.pysus.__aexit__(None, None, None)
- self.exit()
-
- def on_screen_activated(self) -> None:
- if isinstance(self.screen, MainScreen):
- self.populate_local_tree()
-
- def populate_local_tree(self) -> None:
- screen = self.screen
- if not isinstance(screen, MainScreen):
- return
- try:
- tree = screen.query_one("#local-tree", Tree)
- except Exception: # noqa
- return
-
- tree.clear()
- tree.root.expand_all()
- hierarchy = self.pysus.get_local_hierarchy()
-
- status_icons = {
- DownloadStatus.COMPLETED: "ok",
- DownloadStatus.DOWNLOADING: "⏳",
- DownloadStatus.FAILED: "❌",
- DownloadStatus.PENDING: "💤",
- DownloadStatus.MISSING: "❓",
- }
-
- for client, datasets in hierarchy.items():
- client_node = tree.root.add(f"📂 {client}", expand=True)
- for dataset, groups in datasets.items():
- ds_node = client_node.add(f"📦 {dataset}", expand=True)
- for group, files in groups.items():
- parent = ds_node.add(f"📁 {group}") if group else ds_node
- for f in files:
- status = status_icons.get(f["status"], None)
-
- if not status:
- icon = "📄 "
- elif status == "ok":
- icon = ""
- else:
- icon = f"{status} "
-
- parent.add_leaf(f"{icon}{f['name']}", data=f["record"])
-
-
-if __name__ == "__main__":
- app = PySUS(lang="pt")
- app.run()
diff --git a/pysus/tui/i18n.py b/pysus/tui/i18n.py
deleted file mode 100644
index 91067d75..00000000
--- a/pysus/tui/i18n.py
+++ /dev/null
@@ -1,105 +0,0 @@
-TRANSLATIONS: dict[str, dict[str, str | dict[str, str]]] = {
- "en": {
- "welcome": "Welcome to PySUS Client",
- "clients": "Clients",
- "local": "Local",
- "remote": "Remote",
- "search": "Search or leave empty to list all",
- "loading_err": "Failed to load",
- "loading": "Loading",
- "settings": "Settings",
- "quit": "Quit",
- "files": "Files",
- "ftp_browser": "FTP",
- "ducklake_browser": "DuckLake",
- "fetching": "Fetching datasets...",
- "name": "Name",
- "type": "Type",
- "info": "Info",
- "path": "Path",
- "size": "Size",
- "year": "Year",
- "month": "Month",
- "modified": "Modified",
- "state": "State",
- "description": "Description",
- "group": "Group",
- "months": {
- "1": "Jan",
- "2": "Feb",
- "3": "Mar",
- "4": "Apr",
- "5": "May",
- "6": "Jun",
- "7": "Jul",
- "8": "Aug",
- "9": "Sep",
- "10": "Oct",
- "11": "Nov",
- "12": "Dec",
- },
- "esc": "Press ESC to close",
- },
- "pt": {
- "welcome": "Bem-vindo ao Cliente PySUS",
- "clients": "Clientes",
- "local": "Local",
- "remote": "Remoto",
- "search": "Busque ou deixe em branco para listar tudo",
- "loading_err": "Erro ao carregar",
- "loading": "Carregando",
- "settings": "Configurações",
- "quit": "Sair",
- "files": "Arquivos",
- "ftp_browser": "FTP",
- "ducklake_browser": "DuckLake",
- "fetching": "Carregando datasets...",
- "name": "Nome",
- "type": "Tipo",
- "info": "Info",
- "path": "Path",
- "size": "Tamanho",
- "year": "Ano",
- "month": "Mês",
- "modified": "Modificado",
- "state": "Estado",
- "description": "Descrição",
- "group": "Grupo",
- "months": {
- "1": "Jan",
- "2": "Fev",
- "3": "Mar",
- "4": "Abr",
- "5": "Mai",
- "6": "Jun",
- "7": "Jul",
- "8": "Ago",
- "9": "Set",
- "10": "Out",
- "11": "Nov",
- "12": "Dez",
- },
- "esc": "ESC para fechar",
- },
-}
-
-SUPPORTED_LANGUAGES = tuple(TRANSLATIONS.keys())
-
-
-def t(field: str, default: str = "", lang: str = "en") -> str:
- if lang not in TRANSLATIONS:
- lang = "en"
-
- data: dict = TRANSLATIONS[lang]
- keys = field.split(".")
-
- for key in keys:
- value = data.get(key)
- if isinstance(value, str):
- return value
- if isinstance(value, dict):
- data = value
- else:
- return default
-
- return default
diff --git a/pysus/tui/models.py b/pysus/tui/models.py
deleted file mode 100644
index 3be866f0..00000000
--- a/pysus/tui/models.py
+++ /dev/null
@@ -1,258 +0,0 @@
-from dataclasses import dataclass
-from typing import Any
-
-import humanize
-from textual.widgets import DataTable
-
-
-@dataclass
-class SourceRef:
- source: str # "ducklake", "ftp", "local"
- path: str | None = None
- is_downloaded: bool = False
- remote_modified: str | None = None
-
-
-class BaseTUIItem:
- def __init__(self, raw):
- self.raw = raw
- self.name = getattr(raw, "name", str(raw))
- self.type = raw.__class__.__name__
- self._links: list[SourceRef] = []
- self.is_downloading: bool = False
-
- @property
- def source_key(self) -> str:
- parts = [self.name]
- for attr in ("year", "month", "state"):
- val = getattr(self.raw, attr, None)
- if val:
- parts.append(str(val))
- return ":".join(parts)
-
- def add_link(
- self,
- source: str,
- path: str | None = None,
- is_downloaded: bool = False,
- remote_modified: str | None = None,
- ):
- self._links.append(
- SourceRef(source, path, is_downloaded, remote_modified)
- )
-
- @property
- def links(self) -> list[SourceRef]:
- return self._links
-
- def get_columns(self) -> list[str]:
- return [self.name, self.type, "", ""]
-
-
-class File(BaseTUIItem):
- def __init__(
- self,
- raw,
- is_downloaded: bool = False,
- is_downloading: bool = False,
- source: str = "unknown",
- path: str | None = None,
- remote_modified: str | None = None,
- ):
- super().__init__(raw)
- self.is_downloaded = is_downloaded
- self.is_downloading = is_downloading
- self._source = source
- if path:
- self.add_link(source, path, is_downloaded, remote_modified)
-
- @property
- def size(self) -> str:
- raw_size = getattr(self.raw, "size", None)
- if raw_size is not None and isinstance(raw_size, (int, float)):
- return humanize.naturalsize(raw_size, binary=True)
- return "-"
-
- @property
- def modified(self) -> str:
- raw_mod = getattr(self.raw, "modify", None)
- if raw_mod:
- if hasattr(raw_mod, "strftime"):
- return raw_mod.strftime("%Y-%m-%d")
- elif hasattr(raw_mod, "modified"):
- mod = raw_mod.modified
- if hasattr(mod, "strftime"):
- return mod.strftime("%Y-%m-%d")
- raw_dt = getattr(self.raw, "modify_date", None)
- if raw_dt and hasattr(raw_dt, "strftime"):
- return raw_dt.strftime("%Y-%m-%d")
- return "-"
-
- def get_columns(self) -> list[str]:
- display_name = self.name
- link_indicators = []
- sources_seen = set()
- item_type = self.type
- downloaded = self.is_downloaded
-
- if self.is_downloading:
- link_indicators.append("[yellow]◐[/yellow]")
-
- for link in self.links:
- sources_seen.add(link.source)
- if link.is_downloaded:
- downloaded = True
- link_indicators.append("[green]✓[/green]")
-
- if hasattr(self, "_source") and self._source not in sources_seen:
- if downloaded or self.is_downloaded:
- link_indicators = ["[green]✓[/green]"]
-
- if not link_indicators:
- if downloaded or self.is_downloaded:
- link_indicators = ["[green]✓[/green]"]
- elif item_type in (
- "Dataset",
- "BaseRemoteDataset",
- "ConjuntoDados",
- ):
- link_indicators = ["[yellow]📦[/yellow]"]
- item_type = "Dataset"
- elif item_type in ("File", "CatalogFile"):
- link_indicators = ["[yellow] [/yellow]"]
- elif item_type in ("Group", "DatasetGroup"):
- link_indicators = ["[yellow]📁[/yellow]"]
- item_type = "Group"
-
- if link_indicators:
- display_name = f"{display_name} {''.join(link_indicators)}"
-
- long_name = getattr(self.raw, "long_name", None) or ""
- return [display_name, item_type, self.modified, self.size, long_name]
-
-
-class Group(BaseTUIItem):
- def get_columns(self) -> list[str]:
- desc = getattr(self.raw, "long_name", "Directory")
- modified = "-"
- if hasattr(self.raw, "modify") and self.raw.modify:
- if hasattr(self.raw.modify, "strftime"):
- modified = self.raw.modify.strftime("%Y-%m-%d")
- return [self.name, "Group", desc, modified, ""]
-
-
-class Dataset(BaseTUIItem):
- def get_columns(self) -> list[str]:
- long_name = getattr(self.raw, "long_name", self.name)
- modified = "-"
- if hasattr(self.raw, "record") and hasattr(self.raw.record, "modified"):
- mod = self.raw.record.modified
- if hasattr(mod, "strftime"):
- modified = mod.strftime("%Y-%m-%d")
- return [self.name, "File", long_name, modified, ""]
-
-
-class ContentManager:
- def __init__(self):
- self.items: list[BaseTUIItem] = []
- self.filtered: list[BaseTUIItem] = []
- self._item_index: dict[str, BaseTUIItem] = {}
- self._search_text: str | None = None
-
- @property
- def search_text(self) -> str | None:
- return self._search_text
-
- def _normalize_key(self, name: str) -> str:
- return name.replace(".parquet", "").replace(".dbc", "").upper()
-
- def _get_key(self, item: Any) -> str:
- base = self._normalize_key(getattr(item, "name", ""))
- year = getattr(item, "year", None)
- month = getattr(item, "month", None)
- if year:
- base += f":{year}"
- if month:
- base += f":{month:02d}"
- return base
-
- def set_items(
- self,
- raw_items: list,
- downloaded_paths: set[str] | None = None,
- downloading_paths: set[str] | None = None,
- source: str = "unknown",
- clear: bool = True,
- ) -> None:
- if clear:
- self.items = []
- self._item_index = {}
-
- downloaded_paths = downloaded_paths or set()
- downloading_paths = downloading_paths or set()
-
- new_items = []
- for item in raw_items:
- key = self._get_key(item)
- is_done = str(getattr(item, "path", None)) in downloaded_paths
- is_downloading = (
- str(getattr(item, "path", None)) in downloading_paths
- )
- remote_modified = None
-
- if hasattr(item, "remote_modified"):
- remote_modified = str(item.remote_modified)
- elif hasattr(item, "modify_date"):
- remote_modified = str(item.modify_date)
-
- file_obj = File(
- item,
- is_downloaded=is_done,
- is_downloading=is_downloading,
- source=source,
- path=getattr(item, "path", None),
- remote_modified=remote_modified,
- )
-
- if key in self._item_index:
- existing = self._item_index[key]
- for link in file_obj.links:
- existing.add_link(
- link.source,
- link.path,
- link.is_downloaded,
- link.remote_modified,
- )
- else:
- self._item_index[key] = file_obj
- new_items.append(file_obj)
-
- self.items.extend(new_items)
- self.filtered = list(self.items)
-
- def set_downloading(self, path: str, is_downloading: bool) -> None:
- for item in self.items:
- if str(getattr(item.raw, "path", None)) == path:
- item.is_downloading = is_downloading
- break
-
- def apply_filter(self, search_text: str | None) -> None:
- self._search_text = search_text
- if not search_text:
- self.filtered = list(self.items)
- else:
- search_text = search_text.lower()
- self.filtered = [
- item for item in self.items if search_text in item.name.lower()
- ]
-
- def populate(self, table: DataTable, reset_cursor: bool = False) -> None:
- if not reset_cursor:
- cursor_row = table.cursor_row
- else:
- cursor_row = None
- table.clear()
- for item in self.filtered:
- table.add_row(*item.get_columns())
- if cursor_row is not None and cursor_row < table.row_count:
- table.move_cursor(row=cursor_row)
diff --git a/pysus/tui/screens.py b/pysus/tui/screens.py
deleted file mode 100644
index a4d2a6f0..00000000
--- a/pysus/tui/screens.py
+++ /dev/null
@@ -1,410 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import humanize
-from pysus.tui.i18n import TRANSLATIONS, t
-from pysus.tui.models import ContentManager
-from textual import work
-from textual.app import ComposeResult
-from textual.binding import Binding
-from textual.containers import Center, Grid, Horizontal, Middle, Vertical
-from textual.screen import ModalScreen, Screen
-from textual.widgets import (
- Button,
- ContentSwitcher,
- DataTable,
- Footer,
- Header,
- Input,
- Label,
- LoadingIndicator,
- ProgressBar,
- Select,
- Static,
- Switch,
- Tree,
-)
-
-if TYPE_CHECKING:
- from pysus.tui.types import PySUSApp # type: ignore
-
-
-class PySUSScreen(Screen):
- app: PySUSApp
-
-
-def _get_app(screen: Screen) -> PySUSApp:
- return screen.app # type: ignore[return-value]
-
-
-class LoadingScreen(PySUSScreen):
- def compose(self) -> ComposeResult:
- app = _get_app(self)
- lang = app.lang
- yield Header()
- with Vertical(id="loading-container"):
- with Middle():
- yield Static(t("welcome", lang=lang), id="welcome-text")
- yield Static(t("fetching", lang=lang), id="loading-status")
- with Center():
- yield LoadingIndicator(id="loader")
- yield Footer()
-
- def on_key(self, event) -> None:
- if event.key == "q":
- return
- event.stop()
- event.prevent_default()
-
-
-class MainScreen(Screen):
- BINDINGS = [
- Binding("f1", "switch_client('ducklake')", "DuckLake", priority=True),
- Binding("f2", "switch_client('ftp')", "FTP", priority=True),
- # Binding("f3", "switch_client('dadosgov')", "DadosGov", priority=True),
- Binding("f10", "push_screen('config')", "Config", priority=True),
- Binding("i", "show_info", "Info"),
- Binding("d", "download", "Download"),
- Binding("/", "search", "Search"),
- Binding("escape", "back", "Back"),
- Binding("q", "quit", "Quit"),
- ]
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.ducklake_manager = ContentManager()
- self.ftp_manager = ContentManager()
- self.dadosgov_manager = ContentManager()
- self._nav_stack: list[tuple[ContentManager, list]] = []
-
- def compose(self) -> ComposeResult:
- app = _get_app(self)
- lang = app.lang
- yield Header()
- with Horizontal():
- with Vertical(id="main-container"):
- yield Static(t("remote", lang=lang), id="panel-label")
- with ContentSwitcher(id="client-switcher", initial="ducklake"):
- yield DataTable(id="ducklake")
- yield DataTable(id="ftp")
- yield DataTable(id="dadosgov")
- yield Static("", id="download-text")
- yield ProgressBar(id="download-progress", show_percentage=True)
- with Vertical(id="local-sidebar"):
- yield Static(t("local", lang=lang), id="sidebar-label")
- yield Tree(t("files", lang=lang), id="local-tree")
- yield Footer()
-
- async def on_mount(self) -> None:
- app: PySUSApp = self.app
- app.populate_local_tree()
- self.fetch_ducklake()
-
- def action_switch_client(self, client_id: str) -> None:
- switcher = self.query_one("#client-switcher", ContentSwitcher)
- switcher.current = client_id
- self._update_panel_label()
-
- if client_id == "ducklake":
- self.fetch_ducklake()
- elif client_id == "ftp":
- self.fetch_ftp()
- elif client_id == "dadosgov":
- self.fetch_dadosgov()
-
- self.call_later(self._focus_current_table)
-
- def _focus_current_table(self) -> None:
- switcher = self.query_one("#client-switcher", ContentSwitcher)
- table = switcher.query_one(f"#{switcher.current}", DataTable)
- table.focus()
-
- def _update_panel_label(self) -> None:
- try:
- switcher = self.query_one("#client-switcher", ContentSwitcher)
- label = self.query_one("#panel-label", Static)
- client = switcher.current
- if not client:
- client = "ducklake"
- client = client.upper()
- if self._nav_stack:
- label.update(f"{t('remote', lang=self.app.lang)} - {client} ⬅")
- else:
- label.update(f"{t('remote', lang=self.app.lang)} - {client}")
- except Exception: # noqa
- pass
-
- def action_back(self) -> None:
- if not self._nav_stack:
- return
-
- switcher = self.query_one("#client-switcher", ContentSwitcher)
- current_client = switcher.current
- manager = getattr(self, f"{current_client}_manager")
- table = self.query_one(f"#{current_client}", DataTable)
-
- previous = self._nav_stack.pop()
- manager.set_items(previous[1], clear=True)
- manager.populate(table)
- self._update_panel_label()
-
- @work
- async def on_data_table_row_selected(
- self, event: DataTable.RowSelected
- ) -> None:
- switcher = self.query_one("#client-switcher", ContentSwitcher)
- current_client = switcher.current
- table = event.data_table
-
- manager = getattr(self, f"{current_client}_manager")
-
- if event.cursor_row >= len(manager.filtered):
- return
-
- selected_wrapper = manager.filtered[event.cursor_row]
- selected_item = selected_wrapper.raw
-
- table.loading = True
- label = self.query_one("#panel-label", Static)
- label.update(f"{t('loading', lang=self.app.lang)}...")
- self._nav_stack.append((manager, [item.raw for item in manager.items]))
-
- try:
- new_raw_data = []
-
- if hasattr(selected_item, "_fetch_content"):
- new_raw_data = await selected_item._fetch_content()
- elif hasattr(selected_item, "_fetch_files"):
- new_raw_data = await selected_item._fetch_files()
- elif hasattr(selected_item, "groups") and selected_item.groups:
- new_raw_data = [
- g for g in selected_item.groups if hasattr(g, "record")
- ]
- elif hasattr(selected_item, "files") and selected_item.files:
- new_raw_data = list(selected_item.files)
- else:
- if self._nav_stack:
- self._nav_stack.pop()
- table.loading = False
- return
-
- completed_paths = self.app.pysus.get_completed_remote_paths()
-
- manager.set_items(
- new_raw_data,
- downloaded_paths=completed_paths,
- source=current_client,
- )
-
- manager.populate(table)
- self._update_panel_label()
-
- except Exception as e: # noqa
- if self._nav_stack:
- self._nav_stack.pop()
- self.app.notify(f"Navigation Error: {e}", severity="error")
- finally:
- table.loading = False
-
- @work
- async def fetch_ducklake(self) -> None:
- table = self.query_one("#ducklake", DataTable)
- if table.row_count > 0:
- return
-
- table.cursor_type = "row"
- app = _get_app(self)
- lang = app.lang
-
- table.clear(columns=True)
- table.add_columns(
- t("name", lang=lang),
- t("type", lang=lang),
- t("modified", lang=lang),
- t("size", lang=lang),
- t("info", lang=lang),
- )
- table.loading = True
- try:
- ducklake = await app.pysus.get_ducklake()
- datasets = await ducklake.datasets()
- completed_paths = app.pysus.get_completed_remote_paths()
- self.ducklake_manager.set_items(
- datasets, downloaded_paths=completed_paths, source="ducklake"
- )
- self.ducklake_manager.populate(table)
- except Exception as e: # noqa
- app.notify(f"DuckLake Error: {e}", severity="error")
- finally:
- table.loading = False
-
- @work
- async def fetch_ftp(self) -> None:
- table = self.query_one("#ftp", DataTable)
-
- if table.row_count > 0:
- return
-
- table.cursor_type = "row"
- app = _get_app(self)
- lang = app.lang
-
- table.clear(columns=True)
- table.add_columns(
- t("name", lang=lang),
- t("type", lang=lang),
- t("modified", lang=lang),
- t("size", lang=lang),
- t("info", lang=lang),
- )
-
- table.loading = True
- try:
- ftp = await app.pysus.get_ftp()
- files = await ftp.datasets()
- completed_paths = app.pysus.get_completed_remote_paths()
-
- self.ftp_manager.set_items(
- files, downloaded_paths=completed_paths, source="ftp"
- )
- self.ftp_manager.populate(table)
- except Exception as e: # noqa
- app.notify(f"FTP Error: {e}", severity="error")
- finally:
- table.loading = False
-
- @work
- async def fetch_dadosgov(self) -> None:
- app: PySUSApp = self.app
- table = self.query_one("#dadosgov", DataTable)
- if table.row_count > 0:
- return
-
- table.cursor_type = "row"
- lang = app.lang
- table.clear(columns=True)
- table.add_columns(
- t("name", lang=lang),
- t("type", lang=lang),
- t("modified", lang=lang),
- t("size", lang=lang),
- t("info", lang=lang),
- )
- table.loading = True
- try:
- dadosgov = await app.pysus.get_dadosgov()
- datasets = await dadosgov.datasets()
- completed_paths = app.pysus.get_completed_remote_paths()
- self.dadosgov_manager.set_items(
- datasets, downloaded_paths=completed_paths, source="dadosgov"
- )
- self.dadosgov_manager.populate(table)
- except Exception as e: # noqa
- self.app.notify(f"DadosGov Error: {e}", severity="error")
- finally:
- table.loading = False
-
-
-class ConfigScreen(Screen):
- def compose(self) -> ComposeResult:
- app: PySUSApp = self.app
- lang = app.lang
- yield Header()
- with Center():
- with Vertical(id="config-container"):
- yield Static(t("settings", lang=lang), id="config-title")
-
- with Grid(id="config-grid"):
- yield Label("Language / Idioma")
- yield Select(
- [(lang.upper(), lang) for lang in TRANSLATIONS.keys()],
- value=lang,
- id="cfg-lang",
- )
-
- yield Label("Dark Mode")
- yield Switch(value=True, id="cfg-dark")
-
- yield Button("Save & Apply", variant="success", id="cfg-save")
- yield Footer()
-
- def on_button_pressed(self, event: Button.Pressed) -> None:
- app: PySUSApp = self.app
- if event.button.id == "cfg-save":
- new_lang = self.query_one("#cfg-lang", Select).value
- if new_lang:
- app.lang = new_lang
- self.app.pop_screen()
-
-
-class InfoModal(ModalScreen):
- BINDINGS = [("escape", "dismiss", "Close")]
-
- def __init__(self, item, **kwargs):
- super().__init__(**kwargs)
- self.item = item
-
- def compose(self) -> ComposeResult:
- app: PySUSApp = self.app
- name = getattr(self.item, "name", "Unknown")
- long_name = getattr(self.item, "long_name", None)
- title = f"{name} ({long_name})" if long_name else name
- lang = app.lang
-
- with Vertical(id="modal-content-wrapper"):
- yield Static(title, id="modal-title")
-
- info_text = []
- attrs = [
- "description",
- "path",
- "size",
- "year",
- "month",
- "state",
- ]
- for attr in attrs:
- if hasattr(self.item, attr):
- val = getattr(self.item, attr)
- if val:
- label = t(
- attr,
- default=attr.replace("_", " ").title(),
- lang=lang,
- )
-
- if attr == "size":
- val = humanize.naturalsize(val, binary=True)
- elif attr == "month":
- val = t(
- f"months.{val}",
- default=str(val),
- lang=lang,
- )
-
- info_text.append(f"[b]{label}:[/b] {val}")
-
- yield Static(
- "\n".join(info_text) if info_text else "No metadata",
- id="modal-content",
- )
- yield Static(t("esc", lang=lang), id="modal-footer")
-
- def action_dismiss(self) -> None:
- self.dismiss()
-
-
-class SearchModal(ModalScreen):
- def compose(self) -> ComposeResult:
- with Center():
- yield Input(
- placeholder=t("search", default="Search..."),
- id="search-input",
- )
-
- def on_mount(self) -> None:
- self.query_one(Input).focus()
-
- def on_input_submitted(self, event: Input.Submitted) -> None:
- self.dismiss(event.value)
diff --git a/pysus/tui/style.tcss b/pysus/tui/style.tcss
deleted file mode 100644
index 7cd731f0..00000000
--- a/pysus/tui/style.tcss
+++ /dev/null
@@ -1,327 +0,0 @@
-/* =========================
- Color Scheme (High Contrast Dark)
- ========================= */
-
-$primary: #4a5568;
-$secondary: #718096;
-$accent: #9ae6b4;
-$surface: #0d1117;
-$text: #f0f6fc;
-$text-muted: #8b949e;
-
-/* =========================
- Layout
- ========================= */
-
-#main-layout {
- width: 100%;
- height: 100%;
-}
-
-#screen-container {
- width: 100%;
- height: 100%;
-}
-
-#main-container {
- width: 65fr;
- height: 100%;
- border: solid $primary;
-}
-
-#local-sidebar {
- width: 35fr;
- height: 100%;
- border: solid $primary;
- background: $surface;
-}
-
-DataTable {
- height: 1fr;
- border: none;
- padding: 1;
- scrollbar-size: 1 1;
-}
-
-#client-switcher {
- height: 1fr;
-}
-
-#loading-container {
- width: 100%;
- height: 100%;
- align: center middle;
-}
-
-#welcome-text {
- text-align: center;
- text-style: bold;
- color: $accent;
- margin-top: 1;
-}
-
-#loading-status {
- text-align: center;
- color: $text-muted;
- margin-top: 1;
-}
-
-#loader {
- align: center middle;
-}
-
-#screen-container {
- width: 100%;
- height: 100%;
-}
-
-.sidebar {
- width: 30;
- min-width: 30;
- height: 100%;
- dock: right;
- border: solid $primary;
- background: $surface;
- layer: sidebar;
-}
-
-Screen {
- background: transparent;
-}
-
-LoadingScreen Middle {
- width: 100%;
- height: 90%;
- align: center middle;
-}
-
-#main-container {
- width: 65%;
- margin-right: 1;
- border: solid $primary;
- background: transparent;
-}
-
-#sidebar {
- width: 35%;
- border: solid $primary;
- background: $surface;
-}
-
-
-/* =========================
- Typography
- ========================= */
-
-#welcome-text {
- width: 100%;
- text-align: center;
- text-style: bold;
- color: $accent;
-}
-
-#panel-label,
-#sidebar-label {
- padding: 1 2;
- text-style: bold;
- color: $text;
- background: $primary;
-}
-
-#config-title {
- text-align: center;
- text-style: bold;
- margin-bottom: 1;
- color: $accent;
-}
-
-#modal-title {
- width: 100%;
- text-align: center;
- text-style: bold;
- color: $accent;
- border-bottom: solid $primary;
- padding-bottom: 1;
- margin-bottom: 1;
-}
-
-#modal-footer {
- text-align: center;
- color: $text-muted;
- margin-top: 1;
-}
-
-Label {
- height: 3;
- content-align: left middle;
-}
-
-
-/* =========================
- Containers
- ========================= */
-
-#config-container {
- width: 60;
- height: auto;
- padding: 2 3;
- background: $surface;
- border: solid $primary;
-}
-
-#modal-content-wrapper {
- width: 70%;
- height: auto;
- padding: 2 3;
- background: $surface;
- border: solid $primary;
-}
-
-#modal-content {
- margin: 1 0;
- color: $text;
-}
-
-
-/* =========================
- Grid
- ========================= */
-
-#config-grid {
- grid-size: 2;
- grid-columns: 1fr 2fr;
- grid-gutter: 1 2;
-
- height: auto;
- margin-bottom: 2;
-}
-
-
-/* =========================
- Components
- ========================= */
-
-#cfg-save {
- width: 100%;
-}
-
-#loader {
- width: auto;
- height: auto;
-}
-
-DataTable > .datatable--cursor {
- background: $primary;
- color: $text;
-}
-
-DataTable > .datatable--header {
- background: $primary;
- color: $text;
-}
-
-#local-tree {
- height: 95%;
- padding: 1;
- border: none;
- scrollbar-size: 1 1;
-}
-
-#local-tree Tree > .tree--selected {
- background: $primary;
-}
-
-
-/* =========================
- Scrollbar
- ========================= */
-
-ScrollBar {
- width: 1;
-}
-
-ScrollBar > .scrollbar--button {
- display: none;
-}
-
-
-/* =========================
- Progress
- ========================= */
-
-ProgressBar,
-#download-progress {
- width: 100%;
- display: none;
-}
-
-ProgressBar {
- margin: 1 0;
-}
-
-#download-progress {
- margin-top: 1;
-}
-
-ProgressBar.visible,
-#download-progress.visible {
- display: block;
-}
-
-
-/* =========================
- Modal
- ========================= */
-
-ModalScreen {
- align: center middle;
- background: rgba(0, 0, 0, 0.85);
-}
-
-
-/* =========================
- Buttons & Inputs
- ========================= */
-
-Button {
- border: none;
-}
-
-Button:hover {
- background: $primary;
-}
-
-Button:focus {
- border: solid $accent;
-}
-
-Input {
- border: solid $primary;
-}
-
-Input:focus {
- border: solid $accent;
-}
-
-Select {
- border: solid $primary;
-}
-
-Switch {
- color: $accent;
-}
-
-
-/* =========================
- Headers & Footer
- ========================= */
-
-Header {
- background: $primary;
- color: $text;
-}
-
-Footer {
- background: $primary;
- color: $text;
-}
diff --git a/pysus/tui/types.py b/pysus/tui/types.py
deleted file mode 100644
index 4f9dcb89..00000000
--- a/pysus/tui/types.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from typing import Protocol
-
-
-class PySUSApp(Protocol):
- lang: str
-
- def populate_local_tree(self) -> None: ... # noqa
- def notify(self, message: str, severity: str = "info") -> None: ... # noqa
- def push_screen(self, screen, callback=None): ... # noqa
- def pop_screen(self): ... # noqa
- def switch_screen(self, name: str): ... # noqa
-
- class _pysus:
- async def datasets(self): ... # noqa
- def get_completed_remote_paths(self): ... # noqa
- @property
- async def get_ducklake(self): ... # noqa
- @property
- async def get_ftp(self): ... # noqa
- @property
- async def get_dadosgov(self): ... # noqa
-
- @property
- def pysus(self) -> _pysus: ... # noqa