lib-cli-exit-tools 1.0.1__tar.gz → 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/CHANGELOG.md +4 -0
- lib_cli_exit_tools-1.0.2/Makefile +105 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/PKG-INFO +70 -2
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/README.md +68 -1
- lib_cli_exit_tools-1.0.2/docs/textual/README.md +109 -0
- lib_cli_exit_tools-1.0.2/docs/textual/snippets/actions_bindings.py +24 -0
- lib_cli_exit_tools-1.0.2/docs/textual/snippets/listview_basic.py +20 -0
- lib_cli_exit_tools-1.0.2/docs/textual/snippets/mount_await.py +21 -0
- lib_cli_exit_tools-1.0.2/docs/textual/snippets/richlog_stream.py +56 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/brew/Formula/lib-cli-exit-tools.rb +3 -3
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/conda/recipe/meta.yaml +2 -2
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/nix/flake.nix +2 -2
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/pyproject.toml +6 -1
- lib_cli_exit_tools-1.0.2/scripts/__init__.py +3 -0
- lib_cli_exit_tools-1.0.2/scripts/_utils.py +125 -0
- lib_cli_exit_tools-1.0.2/scripts/build.py +37 -0
- lib_cli_exit_tools-1.0.2/scripts/bump.py +27 -0
- lib_cli_exit_tools-1.0.2/scripts/bump_major.py +17 -0
- lib_cli_exit_tools-1.0.2/scripts/bump_minor.py +17 -0
- lib_cli_exit_tools-1.0.2/scripts/bump_patch.py +17 -0
- lib_cli_exit_tools-1.0.2/scripts/clean.py +47 -0
- lib_cli_exit_tools-1.0.2/scripts/dev.py +18 -0
- lib_cli_exit_tools-1.0.2/scripts/install.py +19 -0
- lib_cli_exit_tools-1.0.2/scripts/menu.py +310 -0
- lib_cli_exit_tools-1.0.2/scripts/menu_cli.py +136 -0
- lib_cli_exit_tools-1.0.2/scripts/menu_tui.py +660 -0
- lib_cli_exit_tools-1.0.2/scripts/push.py +33 -0
- lib_cli_exit_tools-1.0.2/scripts/release.py +108 -0
- lib_cli_exit_tools-1.0.2/scripts/run_cli.py +17 -0
- lib_cli_exit_tools-1.0.2/scripts/test.py +83 -0
- lib_cli_exit_tools-1.0.2/scripts/version_current.py +22 -0
- lib_cli_exit_tools-1.0.1/Makefile +0 -253
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.env.example +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.github/workflows/ci.yml +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.github/workflows/codeql.yml +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.github/workflows/release.yml +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.gitignore +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/AGENTS.md +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/CONTRIBUTING.md +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/LICENSE +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/codecov.yml +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/notebooks/Quickstart.ipynb +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/README.md +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/__init__.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/__init__conf__.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/__main__.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/cli.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/py.typed +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_cli.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_cli_extra.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_exit_tools.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_lib_extra.py +0 -0
- {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tools/bump_version.py +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
SHELL := /bin/bash
|
|
2
|
+
.ONESHELL:
|
|
3
|
+
|
|
4
|
+
# Config
|
|
5
|
+
PY ?= python3
|
|
6
|
+
PIP ?= pip
|
|
7
|
+
PKG ?= lib_cli_exit_tools
|
|
8
|
+
GIT_REF ?= v0.1.0
|
|
9
|
+
REMOTE ?= origin
|
|
10
|
+
NIX_FLAKE ?= packaging/nix
|
|
11
|
+
HATCHLING_VERSION ?= 1.25.0
|
|
12
|
+
BREW_FORMULA ?= packaging/brew/Formula/lib-cli-exit-tools.rb
|
|
13
|
+
CONDA_RECIPE ?= packaging/conda/recipe
|
|
14
|
+
FAIL_UNDER ?= 80
|
|
15
|
+
# Coverage mode: on|auto|off (default: on locally)
|
|
16
|
+
# - on : always run coverage
|
|
17
|
+
# - auto : enable on CI or when CODECOV_TOKEN is set
|
|
18
|
+
# - off : never run coverage
|
|
19
|
+
COVERAGE ?= on
|
|
20
|
+
|
|
21
|
+
.PHONY: help install dev test run clean build push release version-current bump bump-patch bump-minor bump-major menu menu-cli menu-tui _bootstrap-dev
|
|
22
|
+
|
|
23
|
+
help: ## Show help
|
|
24
|
+
@grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
|
25
|
+
|
|
26
|
+
install: ## Install package editable
|
|
27
|
+
$(PY) scripts/install.py
|
|
28
|
+
|
|
29
|
+
dev: ## Install package with dev extras
|
|
30
|
+
$(PY) scripts/dev.py
|
|
31
|
+
|
|
32
|
+
_bootstrap-dev:
|
|
33
|
+
@if [ "$(SKIP_BOOTSTRAP)" = "1" ]; then \
|
|
34
|
+
echo "[bootstrap] Skipping dev dependency bootstrap (SKIP_BOOTSTRAP=1)"; \
|
|
35
|
+
else \
|
|
36
|
+
if ! command -v ruff >/dev/null 2>&1 || ! command -v pyright >/dev/null 2>&1 || ! python -c "import pytest" >/dev/null 2>&1; then \
|
|
37
|
+
echo "[bootstrap] Installing dev dependencies via '$(PIP) install -e .[dev]'"; \
|
|
38
|
+
$(PIP) install -e .[dev]; \
|
|
39
|
+
else \
|
|
40
|
+
echo "[bootstrap] Dev tools present"; \
|
|
41
|
+
fi; \
|
|
42
|
+
if ! python -c "import sqlite3" >/dev/null 2>&1; then \
|
|
43
|
+
echo "[bootstrap] sqlite3 stdlib module not available; installing pysqlite3-binary"; \
|
|
44
|
+
$(PIP) install pysqlite3-binary || true; \
|
|
45
|
+
fi; \
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
test: _bootstrap-dev ## Lint, type-check, run tests with coverage, upload to Codecov
|
|
49
|
+
$(PY) scripts/test.py --coverage=$(COVERAGE)
|
|
50
|
+
|
|
51
|
+
run: ## Run module CLI (requires dev install or src on PYTHONPATH)
|
|
52
|
+
$(PY) scripts/run_cli.py -- --help || true
|
|
53
|
+
|
|
54
|
+
version-current: ## Print current version from pyproject.toml
|
|
55
|
+
$(PY) scripts/version_current.py
|
|
56
|
+
|
|
57
|
+
bump: ## Bump version: VERSION=X.Y.Z or PART=major|minor|patch (default: patch); updates pyproject.toml and CHANGELOG.md
|
|
58
|
+
@set -e; \
|
|
59
|
+
if [ -n "$(VERSION)" ]; then \
|
|
60
|
+
$(PY) scripts/bump.py --version "$(VERSION)"; \
|
|
61
|
+
else \
|
|
62
|
+
$(PY) scripts/bump.py --part "$(PART)"; \
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
bump-patch: ## Bump patch version (X.Y.Z -> X.Y.(Z+1))
|
|
66
|
+
$(PY) scripts/bump_patch.py
|
|
67
|
+
|
|
68
|
+
bump-minor: ## Bump minor version (X.Y.Z -> X.(Y+1).0)
|
|
69
|
+
$(PY) scripts/bump_minor.py
|
|
70
|
+
|
|
71
|
+
bump-major: ## Bump major version ((X+1).0.0)
|
|
72
|
+
$(PY) scripts/bump_major.py
|
|
73
|
+
|
|
74
|
+
clean: ## Remove caches, build artifacts, and coverage
|
|
75
|
+
rm -rf \
|
|
76
|
+
.pytest_cache \
|
|
77
|
+
.ruff_cache \
|
|
78
|
+
.pyright \
|
|
79
|
+
.mypy_cache \
|
|
80
|
+
.tox .nox \
|
|
81
|
+
.eggs *.egg-info \
|
|
82
|
+
build dist \
|
|
83
|
+
htmlcov .coverage coverage.xml \
|
|
84
|
+
codecov.sh \
|
|
85
|
+
.cache \
|
|
86
|
+
result
|
|
87
|
+
|
|
88
|
+
push: ## Commit all changes once and push to GitHub (no CI monitoring)
|
|
89
|
+
$(PY) scripts/push.py --remote=$(REMOTE)
|
|
90
|
+
|
|
91
|
+
build: ## Build wheel/sdist and attempt conda, brew, and nix builds (auto-installs tools if missing)
|
|
92
|
+
$(PY) scripts/build.py
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
release: ## Create and push tag vX.Y.Z from pyproject, create GitHub release (if gh present), then sync packaging
|
|
96
|
+
$(PY) scripts/release.py --remote=$(REMOTE)
|
|
97
|
+
|
|
98
|
+
menu: ## Interactive menu (CLI prompts by default)
|
|
99
|
+
$(PY) -u scripts/menu_cli.py < /dev/tty > /dev/tty 2>&1
|
|
100
|
+
|
|
101
|
+
menu-cli: ## Force simple CLI prompt menu (no TUI)
|
|
102
|
+
$(PY) -u scripts/menu_cli.py < /dev/tty > /dev/tty 2>&1
|
|
103
|
+
|
|
104
|
+
menu-tui: ## Force Textual TUI menu (may not work on limited terminals)
|
|
105
|
+
$(PY) -u scripts/menu_tui.py < /dev/tty > /dev/tty 2>&1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_cli_exit_tools
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: CLI exit handling helpers: clean signals, exit codes, and error printing
|
|
5
5
|
Project-URL: Homepage, https://github.com/bitranox/lib_cli_exit_tools
|
|
6
6
|
Project-URL: Repository, https://github.com/bitranox/lib_cli_exit_tools.git
|
|
@@ -24,6 +24,7 @@ Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
|
24
24
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
25
25
|
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
26
26
|
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
27
|
+
Requires-Dist: textual>=0.50; extra == 'dev'
|
|
27
28
|
Requires-Dist: twine>=5.1; extra == 'dev'
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
29
30
|
|
|
@@ -135,13 +136,80 @@ pip install .
|
|
|
135
136
|
| `test` | Lint, type-check, run tests with coverage, upload to Codecov |
|
|
136
137
|
| `run` | Run module CLI (requires dev install or src on PYTHONPATH) |
|
|
137
138
|
| `version-current` | Print current version from pyproject.toml |
|
|
138
|
-
| `bump` | Bump version
|
|
139
|
+
| `bump` | Bump version (updates pyproject.toml and CHANGELOG.md) |
|
|
139
140
|
| `bump-patch` | Bump patch version (X.Y.Z -> X.Y.(Z+1)) |
|
|
140
141
|
| `bump-minor` | Bump minor version (X.Y.Z -> X.(Y+1).0) |
|
|
141
142
|
| `bump-major` | Bump major version ((X+1).0.0) |
|
|
142
143
|
| `clean` | Remove caches, build artifacts, and coverage |
|
|
143
144
|
| `push` | Commit all changes once and push to GitHub (no CI monitoring) |
|
|
144
145
|
| `build` | Build wheel/sdist and attempt conda, brew, and nix builds (auto-installs tools if missing) |
|
|
146
|
+
| `menu` | Interactive TUI to run targets and edit parameters (requires dev dep: textual) |
|
|
147
|
+
| `menu-cli` | Force simple prompt menu (no TUI) |
|
|
148
|
+
| `menu-tui` | Force Textual TUI (may not work in limited terminals) |
|
|
149
|
+
|
|
150
|
+
#### Target Parameters (env vars)
|
|
151
|
+
|
|
152
|
+
- Global
|
|
153
|
+
- `PY` (default: `python3`) — Python interpreter used to run scripts
|
|
154
|
+
- `PIP` (default: `pip`) — pip executable used by bootstrap/install
|
|
155
|
+
|
|
156
|
+
- `install`
|
|
157
|
+
- No specific parameters (respects `PY`, `PIP`).
|
|
158
|
+
|
|
159
|
+
- `dev`
|
|
160
|
+
- No specific parameters (respects `PY`, `PIP`).
|
|
161
|
+
|
|
162
|
+
- `test`
|
|
163
|
+
- `COVERAGE=on|auto|off` (default: `on`) — controls pytest coverage run and Codecov upload
|
|
164
|
+
- `SKIP_BOOTSTRAP=1` — skip auto-install of dev tools if missing
|
|
165
|
+
- Also respects `CODECOV_TOKEN` when needed for uploads
|
|
166
|
+
|
|
167
|
+
- `run`
|
|
168
|
+
- No parameters via `make` (always shows `--help`). For custom args: `python scripts/run_cli.py -- <args>`.
|
|
169
|
+
|
|
170
|
+
- `version-current`
|
|
171
|
+
- No parameters
|
|
172
|
+
|
|
173
|
+
- `bump`
|
|
174
|
+
- `VERSION=X.Y.Z` — explicit target version
|
|
175
|
+
- `PART=major|minor|patch` — semantic part to bump (default if `VERSION` not set: `patch`)
|
|
176
|
+
- Examples:
|
|
177
|
+
- `make bump VERSION=1.0.2`
|
|
178
|
+
- `make bump PART=minor`
|
|
179
|
+
|
|
180
|
+
- `bump-patch` / `bump-minor` / `bump-major`
|
|
181
|
+
- No parameters; shorthand for `make bump PART=...`
|
|
182
|
+
|
|
183
|
+
- `clean`
|
|
184
|
+
- No parameters
|
|
185
|
+
|
|
186
|
+
- `push`
|
|
187
|
+
- `REMOTE=<name>` (default: `origin`) — git remote to push to
|
|
188
|
+
|
|
189
|
+
- `build`
|
|
190
|
+
- No parameters via `make`. Advanced: use the script directly, e.g. `python scripts/build.py --no-conda --no-nix`.
|
|
191
|
+
|
|
192
|
+
- `release`
|
|
193
|
+
- `REMOTE=<name>` (default: `origin`) — git remote to push to
|
|
194
|
+
- Advanced (via script): `python scripts/release.py --retries 5 --retry-wait 3.0`
|
|
195
|
+
|
|
196
|
+
### Interactive Menu (Textual)
|
|
197
|
+
|
|
198
|
+
`make menu` launches a colorful terminal UI (powered by `textual`) to browse targets, edit parameters, and run them with live output. If the terminal doesn’t support a full TUI or `textual` can’t load, it falls back to a simple prompt menu.
|
|
199
|
+
|
|
200
|
+
Install dev extras if you haven’t:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pip install -e .[dev]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Run the menu:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
make menu
|
|
210
|
+
make menu-cli # always use simple prompt menu
|
|
211
|
+
make menu-tui # force TUI (if your terminal supports it)
|
|
212
|
+
```
|
|
145
213
|
|
|
146
214
|
#### Target Details
|
|
147
215
|
|
|
@@ -106,13 +106,80 @@ pip install .
|
|
|
106
106
|
| `test` | Lint, type-check, run tests with coverage, upload to Codecov |
|
|
107
107
|
| `run` | Run module CLI (requires dev install or src on PYTHONPATH) |
|
|
108
108
|
| `version-current` | Print current version from pyproject.toml |
|
|
109
|
-
| `bump` | Bump version
|
|
109
|
+
| `bump` | Bump version (updates pyproject.toml and CHANGELOG.md) |
|
|
110
110
|
| `bump-patch` | Bump patch version (X.Y.Z -> X.Y.(Z+1)) |
|
|
111
111
|
| `bump-minor` | Bump minor version (X.Y.Z -> X.(Y+1).0) |
|
|
112
112
|
| `bump-major` | Bump major version ((X+1).0.0) |
|
|
113
113
|
| `clean` | Remove caches, build artifacts, and coverage |
|
|
114
114
|
| `push` | Commit all changes once and push to GitHub (no CI monitoring) |
|
|
115
115
|
| `build` | Build wheel/sdist and attempt conda, brew, and nix builds (auto-installs tools if missing) |
|
|
116
|
+
| `menu` | Interactive TUI to run targets and edit parameters (requires dev dep: textual) |
|
|
117
|
+
| `menu-cli` | Force simple prompt menu (no TUI) |
|
|
118
|
+
| `menu-tui` | Force Textual TUI (may not work in limited terminals) |
|
|
119
|
+
|
|
120
|
+
#### Target Parameters (env vars)
|
|
121
|
+
|
|
122
|
+
- Global
|
|
123
|
+
- `PY` (default: `python3`) — Python interpreter used to run scripts
|
|
124
|
+
- `PIP` (default: `pip`) — pip executable used by bootstrap/install
|
|
125
|
+
|
|
126
|
+
- `install`
|
|
127
|
+
- No specific parameters (respects `PY`, `PIP`).
|
|
128
|
+
|
|
129
|
+
- `dev`
|
|
130
|
+
- No specific parameters (respects `PY`, `PIP`).
|
|
131
|
+
|
|
132
|
+
- `test`
|
|
133
|
+
- `COVERAGE=on|auto|off` (default: `on`) — controls pytest coverage run and Codecov upload
|
|
134
|
+
- `SKIP_BOOTSTRAP=1` — skip auto-install of dev tools if missing
|
|
135
|
+
- Also respects `CODECOV_TOKEN` when needed for uploads
|
|
136
|
+
|
|
137
|
+
- `run`
|
|
138
|
+
- No parameters via `make` (always shows `--help`). For custom args: `python scripts/run_cli.py -- <args>`.
|
|
139
|
+
|
|
140
|
+
- `version-current`
|
|
141
|
+
- No parameters
|
|
142
|
+
|
|
143
|
+
- `bump`
|
|
144
|
+
- `VERSION=X.Y.Z` — explicit target version
|
|
145
|
+
- `PART=major|minor|patch` — semantic part to bump (default if `VERSION` not set: `patch`)
|
|
146
|
+
- Examples:
|
|
147
|
+
- `make bump VERSION=1.0.2`
|
|
148
|
+
- `make bump PART=minor`
|
|
149
|
+
|
|
150
|
+
- `bump-patch` / `bump-minor` / `bump-major`
|
|
151
|
+
- No parameters; shorthand for `make bump PART=...`
|
|
152
|
+
|
|
153
|
+
- `clean`
|
|
154
|
+
- No parameters
|
|
155
|
+
|
|
156
|
+
- `push`
|
|
157
|
+
- `REMOTE=<name>` (default: `origin`) — git remote to push to
|
|
158
|
+
|
|
159
|
+
- `build`
|
|
160
|
+
- No parameters via `make`. Advanced: use the script directly, e.g. `python scripts/build.py --no-conda --no-nix`.
|
|
161
|
+
|
|
162
|
+
- `release`
|
|
163
|
+
- `REMOTE=<name>` (default: `origin`) — git remote to push to
|
|
164
|
+
- Advanced (via script): `python scripts/release.py --retries 5 --retry-wait 3.0`
|
|
165
|
+
|
|
166
|
+
### Interactive Menu (Textual)
|
|
167
|
+
|
|
168
|
+
`make menu` launches a colorful terminal UI (powered by `textual`) to browse targets, edit parameters, and run them with live output. If the terminal doesn’t support a full TUI or `textual` can’t load, it falls back to a simple prompt menu.
|
|
169
|
+
|
|
170
|
+
Install dev extras if you haven’t:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pip install -e .[dev]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Run the menu:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
make menu
|
|
180
|
+
make menu-cli # always use simple prompt menu
|
|
181
|
+
make menu-tui # force TUI (if your terminal supports it)
|
|
182
|
+
```
|
|
116
183
|
|
|
117
184
|
#### Target Details
|
|
118
185
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
(for this repo)
|
|
2
|
+
|
|
3
|
+
makThis is a pragmatic quick‑reference for building and debugging Textual UIs that match the versions we use here (Textual ≥ 6.x). It focuses on patterns we implemented in `scripts/menu_tui.py` (ListView + RichLog + async subprocess + Cancel).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
- Dev extras (recommended):
|
|
8
|
+
- `pip install -e .[dev]`
|
|
9
|
+
- Or just Textual: `pip install textual`
|
|
10
|
+
- Verify: `python -c "import textual,sys; print(sys.version, textual.__version__)"`
|
|
11
|
+
|
|
12
|
+
## App + compose
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from textual.app import App, ComposeResult
|
|
16
|
+
from textual.widgets import Header, Footer, Static
|
|
17
|
+
|
|
18
|
+
class Hello(App):
|
|
19
|
+
def compose(self) -> ComposeResult:
|
|
20
|
+
yield Header()
|
|
21
|
+
yield Static("Hello Textual")
|
|
22
|
+
yield Footer()
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
Hello().run()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Mounting: schedule vs await
|
|
29
|
+
|
|
30
|
+
- `self.mount(widget)` schedules. If you need to update/query the widget immediately, `await self.mount(widget)` inside an async handler (e.g. `async def on_mount(...)`).
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# See snippets/mount_await.py
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## ListView (compose safely)
|
|
37
|
+
|
|
38
|
+
Create items inside `compose` or after mount. For initial items:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from textual.widgets import ListView, ListItem, Label
|
|
42
|
+
|
|
43
|
+
yield ListView(
|
|
44
|
+
ListItem(Label("Item 1")),
|
|
45
|
+
ListItem(Label("Item 2")),
|
|
46
|
+
id="targets",
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Adding later (after mount):
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
lv = self.query_one("#targets", ListView)
|
|
54
|
+
await lv.append(ListItem(Label("New Item"))) # or lv.append(...) if in compose tick
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## RichLog (Textual 6.x)
|
|
58
|
+
|
|
59
|
+
- Use `RichLog` (not `TextLog`).
|
|
60
|
+
- Append with `write()`.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from textual.widgets import RichLog
|
|
64
|
+
log = RichLog(highlight=True)
|
|
65
|
+
log.write("Starting…")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Async subprocess + live streaming
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
# See snippets/richlog_stream.py
|
|
72
|
+
# gist: create_subprocess_exec + read stdout/stderr per line → log.write()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
POSIX Cancel: `proc.send_signal(signal.SIGINT)`; Windows: `proc.terminate()`.
|
|
76
|
+
|
|
77
|
+
## Actions & key bindings
|
|
78
|
+
|
|
79
|
+
Bind keys to actions:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# See snippets/actions_bindings.py
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CSS basics (inline)
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
CSS = """
|
|
89
|
+
Screen { layout: horizontal; }
|
|
90
|
+
#right { width: 1fr; }
|
|
91
|
+
RichLog { border: tall $accent; }
|
|
92
|
+
"""
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Run the examples
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python docs/textual/snippets/listview_basic.py
|
|
99
|
+
python docs/textual/snippets/richlog_stream.py
|
|
100
|
+
python docs/textual/snippets/mount_await.py
|
|
101
|
+
python docs/textual/snippets/actions_bindings.py
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Troubleshooting
|
|
105
|
+
|
|
106
|
+
- Nothing shows under `make`: ensure stdin/stdout is a TTY. We wire `/dev/tty` in Make.
|
|
107
|
+
- Textual import errors: check version (`textual.__version__`), install `pip install -e .[dev]`.
|
|
108
|
+
- MountError on ListView: don’t append before the ListView is mounted; yield items in `compose` or `await mount()` first.
|
|
109
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from textual.app import App, ComposeResult
|
|
4
|
+
from textual.widgets import Header, Footer, Static
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BindingApp(App):
|
|
8
|
+
BINDINGS = [
|
|
9
|
+
("r", "set_bg('red')", "Red"),
|
|
10
|
+
("g", "set_bg('green')", "Green"),
|
|
11
|
+
("b", "set_bg('blue')", "Blue"),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
def compose(self) -> ComposeResult:
|
|
15
|
+
yield Header()
|
|
16
|
+
yield Static("Press R/G/B to change background")
|
|
17
|
+
yield Footer()
|
|
18
|
+
|
|
19
|
+
def action_set_bg(self, color: str) -> None:
|
|
20
|
+
self.screen.styles.background = color
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
BindingApp().run()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from textual.app import App, ComposeResult
|
|
4
|
+
from textual.widgets import Header, Footer, ListView, ListItem, Label
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ListViewDemo(App):
|
|
8
|
+
def compose(self) -> ComposeResult:
|
|
9
|
+
yield Header()
|
|
10
|
+
yield ListView(
|
|
11
|
+
ListItem(Label("Install")),
|
|
12
|
+
ListItem(Label("Build")),
|
|
13
|
+
ListItem(Label("Release")),
|
|
14
|
+
id="targets",
|
|
15
|
+
)
|
|
16
|
+
yield Footer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
ListViewDemo().run()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from textual.app import App, ComposeResult
|
|
4
|
+
from textual.widgets import Header, Footer, Static, Button
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MountAwaitApp(App):
|
|
8
|
+
def compose(self) -> ComposeResult:
|
|
9
|
+
yield Header()
|
|
10
|
+
yield Button("Add Box", id="add")
|
|
11
|
+
yield Footer()
|
|
12
|
+
|
|
13
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
14
|
+
if event.button.id == "add":
|
|
15
|
+
box = Static("I was mounted and styled!")
|
|
16
|
+
await self.mount(box)
|
|
17
|
+
box.styles.border = ("tall", "green")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
MountAwaitApp().run()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from textual.app import App, ComposeResult
|
|
5
|
+
from textual.widgets import Header, Footer, RichLog, Button
|
|
6
|
+
from textual.containers import Horizontal
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StreamApp(App):
|
|
10
|
+
CSS = """
|
|
11
|
+
RichLog { border: tall $accent; }
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def compose(self) -> ComposeResult:
|
|
15
|
+
yield Header()
|
|
16
|
+
self.log = RichLog(highlight=True)
|
|
17
|
+
yield self.log
|
|
18
|
+
with Horizontal():
|
|
19
|
+
yield Button("Run", id="run")
|
|
20
|
+
yield Button("Cancel", id="cancel")
|
|
21
|
+
yield Footer()
|
|
22
|
+
|
|
23
|
+
async def _run_cmd(self) -> None:
|
|
24
|
+
self._proc = await asyncio.create_subprocess_exec(
|
|
25
|
+
"python",
|
|
26
|
+
"-c",
|
|
27
|
+
"import time; [print(i) or time.sleep(0.3) for i in range(10)]",
|
|
28
|
+
stdout=asyncio.subprocess.PIPE,
|
|
29
|
+
stderr=asyncio.subprocess.PIPE,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
async def pump(stream, label: str):
|
|
33
|
+
assert stream is not None
|
|
34
|
+
while True:
|
|
35
|
+
line = await stream.readline()
|
|
36
|
+
if not line:
|
|
37
|
+
break
|
|
38
|
+
self.log.write(f"{label} {line.decode().rstrip()}\n")
|
|
39
|
+
|
|
40
|
+
await asyncio.gather(pump(self._proc.stdout, "stdout:"), pump(self._proc.stderr, "stderr:"))
|
|
41
|
+
rc = await self._proc.wait()
|
|
42
|
+
self.log.write(f"exit={rc}")
|
|
43
|
+
|
|
44
|
+
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
45
|
+
if event.button.id == "run":
|
|
46
|
+
self.set_timer(0, lambda: asyncio.create_task(self._run_cmd()))
|
|
47
|
+
elif event.button.id == "cancel":
|
|
48
|
+
if getattr(self, "_proc", None):
|
|
49
|
+
try:
|
|
50
|
+
self._proc.send_signal(getattr(asyncio.subprocess, "signal", __import__("signal")).SIGINT)
|
|
51
|
+
except Exception:
|
|
52
|
+
self._proc.terminate()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
StreamApp().run()
|
{lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/brew/Formula/lib-cli-exit-tools.rb
RENAMED
|
@@ -3,8 +3,8 @@ class LibCliExitTools < Formula
|
|
|
3
3
|
|
|
4
4
|
desc "CLI exit handling helpers: clean signals, exit codes, and error printing"
|
|
5
5
|
homepage "https://github.com/bitranox/lib_cli_exit_tools"
|
|
6
|
-
url "https://github.com/bitranox/lib_cli_exit_tools/archive/refs/tags/v1.0.
|
|
7
|
-
|
|
6
|
+
url "https://github.com/bitranox/lib_cli_exit_tools/archive/refs/tags/v1.0.2.tar.gz"
|
|
7
|
+
k3cc316e7d0b09dd79e617cb3e91b14ee414b7e6695b108a326d9d3ccd08dd9"
|
|
8
8
|
license "MIT"
|
|
9
9
|
|
|
10
10
|
depends_on "python@3.10"
|
|
@@ -12,7 +12,7 @@ class LibCliExitTools < Formula
|
|
|
12
12
|
# Vendor Python deps (fill versions/sha256 for an actual formula)
|
|
13
13
|
resource "click" do
|
|
14
14
|
url "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz"
|
|
15
|
-
|
|
15
|
+
k3cc316e7d0b09dd79e617cb3e91b14ee414b7e6695b108a326d9d3ccd08dd9"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def install
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{% set name = "lib_cli_exit_tools" %}
|
|
2
|
-
{% set version = "1.0.
|
|
2
|
+
{% set version = "1.0.2" %}
|
|
3
3
|
|
|
4
4
|
package:
|
|
5
5
|
name: {{ name|lower }}
|
|
@@ -13,7 +13,7 @@ source:
|
|
|
13
13
|
source:
|
|
14
14
|
# For released tags; replace sha256 before submitting to conda-forge
|
|
15
15
|
url: https://github.com/bitranox/{{ name }}/archive/refs/tags/v{{ version }}.tar.gz
|
|
16
|
-
|
|
16
|
+
k3cc316e7d0b09dd79e617cb3e91b14ee414b7e6695b108a326d9d3ccd08dd9"
|
|
17
17
|
{% endif %}
|
|
18
18
|
|
|
19
19
|
build:
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
{
|
|
36
36
|
packages.default = pypkgs.buildPythonPackage {
|
|
37
37
|
pname = "lib_cli_exit_tools";
|
|
38
|
-
version = "1.0.
|
|
38
|
+
version = "1.0.2";
|
|
39
39
|
pyproject = true;
|
|
40
40
|
# Build from the repository root (two levels up from packaging/nix)
|
|
41
41
|
src = ../..;
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
# src = pkgs.fetchFromGitHub {
|
|
44
44
|
# owner = "bitranox";
|
|
45
45
|
# repo = "lib_cli_exit_tools";
|
|
46
|
-
# rev = "v1.0.
|
|
46
|
+
# rev = "v1.0.2";
|
|
47
47
|
# sha256 = "<fill-me>";
|
|
48
48
|
# };
|
|
49
49
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lib_cli_exit_tools"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.2"
|
|
4
4
|
description = "CLI exit handling helpers: clean signals, exit codes, and error printing"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -31,6 +31,7 @@ dev = [
|
|
|
31
31
|
"build>=1.2",
|
|
32
32
|
"twine>=5.1",
|
|
33
33
|
"codecov>=2.1.13",
|
|
34
|
+
"textual>=0.50",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.scripts]
|
|
@@ -59,6 +60,10 @@ target-version = "py313"
|
|
|
59
60
|
|
|
60
61
|
[tool.pyright]
|
|
61
62
|
typeCheckingMode = "strict"
|
|
63
|
+
exclude = [
|
|
64
|
+
"scripts/menu.py",
|
|
65
|
+
"docs",
|
|
66
|
+
]
|
|
62
67
|
|
|
63
68
|
[tool.pytest.ini_options]
|
|
64
69
|
addopts = ["--doctest-modules"]
|