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.
Files changed (54) hide show
  1. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/CHANGELOG.md +4 -0
  2. lib_cli_exit_tools-1.0.2/Makefile +105 -0
  3. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/PKG-INFO +70 -2
  4. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/README.md +68 -1
  5. lib_cli_exit_tools-1.0.2/docs/textual/README.md +109 -0
  6. lib_cli_exit_tools-1.0.2/docs/textual/snippets/actions_bindings.py +24 -0
  7. lib_cli_exit_tools-1.0.2/docs/textual/snippets/listview_basic.py +20 -0
  8. lib_cli_exit_tools-1.0.2/docs/textual/snippets/mount_await.py +21 -0
  9. lib_cli_exit_tools-1.0.2/docs/textual/snippets/richlog_stream.py +56 -0
  10. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/brew/Formula/lib-cli-exit-tools.rb +3 -3
  11. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/conda/recipe/meta.yaml +2 -2
  12. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/nix/flake.nix +2 -2
  13. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/pyproject.toml +6 -1
  14. lib_cli_exit_tools-1.0.2/scripts/__init__.py +3 -0
  15. lib_cli_exit_tools-1.0.2/scripts/_utils.py +125 -0
  16. lib_cli_exit_tools-1.0.2/scripts/build.py +37 -0
  17. lib_cli_exit_tools-1.0.2/scripts/bump.py +27 -0
  18. lib_cli_exit_tools-1.0.2/scripts/bump_major.py +17 -0
  19. lib_cli_exit_tools-1.0.2/scripts/bump_minor.py +17 -0
  20. lib_cli_exit_tools-1.0.2/scripts/bump_patch.py +17 -0
  21. lib_cli_exit_tools-1.0.2/scripts/clean.py +47 -0
  22. lib_cli_exit_tools-1.0.2/scripts/dev.py +18 -0
  23. lib_cli_exit_tools-1.0.2/scripts/install.py +19 -0
  24. lib_cli_exit_tools-1.0.2/scripts/menu.py +310 -0
  25. lib_cli_exit_tools-1.0.2/scripts/menu_cli.py +136 -0
  26. lib_cli_exit_tools-1.0.2/scripts/menu_tui.py +660 -0
  27. lib_cli_exit_tools-1.0.2/scripts/push.py +33 -0
  28. lib_cli_exit_tools-1.0.2/scripts/release.py +108 -0
  29. lib_cli_exit_tools-1.0.2/scripts/run_cli.py +17 -0
  30. lib_cli_exit_tools-1.0.2/scripts/test.py +83 -0
  31. lib_cli_exit_tools-1.0.2/scripts/version_current.py +22 -0
  32. lib_cli_exit_tools-1.0.1/Makefile +0 -253
  33. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.env.example +0 -0
  34. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.github/workflows/ci.yml +0 -0
  35. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.github/workflows/codeql.yml +0 -0
  36. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.github/workflows/release.yml +0 -0
  37. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/.gitignore +0 -0
  38. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/AGENTS.md +0 -0
  39. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/CONTRIBUTING.md +0 -0
  40. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/LICENSE +0 -0
  41. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/codecov.yml +0 -0
  42. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/notebooks/Quickstart.ipynb +0 -0
  43. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/packaging/README.md +0 -0
  44. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/__init__.py +0 -0
  45. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/__init__conf__.py +0 -0
  46. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/__main__.py +0 -0
  47. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/cli.py +0 -0
  48. {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
  49. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/src/lib_cli_exit_tools/py.typed +0 -0
  50. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_cli.py +0 -0
  51. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_cli_extra.py +0 -0
  52. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_exit_tools.py +0 -0
  53. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tests/test_lib_extra.py +0 -0
  54. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.2}/tools/bump_version.py +0 -0
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.2] - 2025-09-15
4
+
5
+ - _Describe changes here._
6
+
3
7
  ## [1.0.1] - 2025-09-15
4
8
 
5
9
  - _Describe changes here._
@@ -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.1
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: VERSION=X.Y.Z or PART=major |minor|patch (default: patch); updates pyproject.toml and CHANGELOG.md |
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: VERSION=X.Y.Z or PART=major |minor|patch (default: patch); updates pyproject.toml and CHANGELOG.md |
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()
@@ -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.1.tar.gz"
7
- sha256 "<fill-me>"
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
- sha256 "27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"
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.1" %}
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
- sha256: "<fill-me>"
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.1";
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.1";
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.1"
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"]
@@ -0,0 +1,3 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = []