lib-cli-exit-tools 1.0.1__tar.gz → 1.0.3__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 (48) hide show
  1. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/CHANGELOG.md +10 -2
  2. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/CONTRIBUTING.md +1 -1
  3. lib_cli_exit_tools-1.0.3/Makefile +98 -0
  4. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/PKG-INFO +68 -3
  5. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/README.md +66 -2
  6. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/codecov.yml +1 -1
  7. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/packaging/README.md +2 -2
  8. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/packaging/brew/Formula/lib-cli-exit-tools.rb +3 -3
  9. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/packaging/conda/recipe/meta.yaml +2 -2
  10. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/packaging/nix/flake.nix +2 -2
  11. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/pyproject.toml +5 -1
  12. lib_cli_exit_tools-1.0.3/qlty.toml +2 -0
  13. lib_cli_exit_tools-1.0.3/scripts/__init__.py +3 -0
  14. lib_cli_exit_tools-1.0.3/scripts/_utils.py +125 -0
  15. lib_cli_exit_tools-1.0.3/scripts/build.py +37 -0
  16. lib_cli_exit_tools-1.0.3/scripts/bump.py +27 -0
  17. lib_cli_exit_tools-1.0.3/scripts/bump_major.py +17 -0
  18. lib_cli_exit_tools-1.0.3/scripts/bump_minor.py +17 -0
  19. lib_cli_exit_tools-1.0.3/scripts/bump_patch.py +17 -0
  20. {lib_cli_exit_tools-1.0.1/tools → lib_cli_exit_tools-1.0.3/scripts}/bump_version.py +1 -1
  21. lib_cli_exit_tools-1.0.3/scripts/clean.py +47 -0
  22. lib_cli_exit_tools-1.0.3/scripts/dev.py +18 -0
  23. lib_cli_exit_tools-1.0.3/scripts/install.py +19 -0
  24. lib_cli_exit_tools-1.0.3/scripts/menu.py +643 -0
  25. lib_cli_exit_tools-1.0.3/scripts/push.py +33 -0
  26. lib_cli_exit_tools-1.0.3/scripts/release.py +108 -0
  27. lib_cli_exit_tools-1.0.3/scripts/run_cli.py +17 -0
  28. lib_cli_exit_tools-1.0.3/scripts/test.py +96 -0
  29. lib_cli_exit_tools-1.0.3/scripts/version_current.py +22 -0
  30. lib_cli_exit_tools-1.0.1/Makefile +0 -253
  31. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/.env.example +0 -0
  32. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/.github/workflows/ci.yml +0 -0
  33. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/.github/workflows/codeql.yml +0 -0
  34. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/.github/workflows/release.yml +0 -0
  35. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/.gitignore +0 -0
  36. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/AGENTS.md +0 -0
  37. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/LICENSE +0 -0
  38. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/notebooks/Quickstart.ipynb +0 -0
  39. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/src/lib_cli_exit_tools/__init__.py +0 -0
  40. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/src/lib_cli_exit_tools/__init__conf__.py +0 -0
  41. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/src/lib_cli_exit_tools/__main__.py +0 -0
  42. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/src/lib_cli_exit_tools/cli.py +0 -0
  43. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +0 -0
  44. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/src/lib_cli_exit_tools/py.typed +0 -0
  45. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/tests/test_cli.py +0 -0
  46. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/tests/test_cli_extra.py +0 -0
  47. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/tests/test_exit_tools.py +0 -0
  48. {lib_cli_exit_tools-1.0.1 → lib_cli_exit_tools-1.0.3}/tests/test_lib_extra.py +0 -0
@@ -1,8 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.3] - 2025-09-16
4
+
5
+ - added make menu
6
+
7
+ ## [1.0.2] - 2025-09-15
8
+
9
+ - minor internals
10
+
3
11
  ## [1.0.1] - 2025-09-15
4
12
 
5
- - _Describe changes here._
13
+ - minor internals
6
14
 
7
15
  ## [1.0.0] - 2025-09-15
8
16
 
@@ -12,7 +20,7 @@
12
20
 
13
21
  ### Added
14
22
  - CI job to execute Jupyter notebook on Python 3.10 (installs ipykernel, normalizes notebook IDs) and a tag‑time packaging‑consistency check.
15
- - Packaging auto‑sync: `make test`, `make push`, and the bump script now align Conda/Brew/Nix files to `pyproject.toml` version, `requires-python`, and runtime deps. Homebrew resources are updated (URL + sha256) using PyPI metadata. A sync‑only mode is available via `python tools/bump_version.py --sync-packaging`.
23
+ - Packaging auto‑sync: `make test`, `make push`, and the bump script now align Conda/Brew/Nix files to `pyproject.toml` version, `requires-python`, and runtime deps. Homebrew resources are updated (URL + sha256) using PyPI metadata. A sync‑only mode is available via `python scripts/bump_version.py --sync-packaging`.
16
24
  - Tests covering SystemExit variants, tolerant output printing, English CLI messages, and Windows mapping via monkeypatched `os.name`.
17
25
 
18
26
  ### Changed
@@ -28,7 +28,7 @@ Thanks for helping improve lib_cli_exit_tools! This guide keeps changes small, s
28
28
  - Homebrew: ensures a `resource "<dep>"` for each dep; if pinned, uses that version; otherwise pins to the latest PyPI release, updating `url` + `sha256`.
29
29
  - Nix: updates the package version, `pkgs.pythonXYZPackages`/`pkgs.pythonXYZ` to match the min Python, and `propagatedBuildInputs` to include all deps as `pypkgs.<name>`.
30
30
  - Network note: Homebrew resource updates fetch PyPI metadata; if offline, those updates may be skipped.
31
- - If packaging drifts, run: `python tools/bump_version.py --sync-packaging`.
31
+ - If packaging drifts, run: `python scripts/bump_version.py --sync-packaging`.
32
32
  - CI enforces consistency on tags via a `packaging-consistency` job.
33
33
 
34
34
  ## 5) Tests & Style
@@ -0,0 +1,98 @@
1
+ SHELL := /bin/bash
2
+ .ONESHELL:
3
+ # Config
4
+ PY ?= python3
5
+ PIP ?= pip
6
+ PKG ?= lib_cli_exit_tools
7
+ GIT_REF ?= v0.1.0
8
+ REMOTE ?= origin
9
+ NIX_FLAKE ?= packaging/nix
10
+ HATCHLING_VERSION ?= 1.25.0
11
+ BREW_FORMULA ?= packaging/brew/Formula/lib-cli-exit-tools.rb
12
+ CONDA_RECIPE ?= packaging/conda/recipe
13
+ FAIL_UNDER ?= 80
14
+ # Coverage mode: on|auto|off (default: on locally)
15
+ # - on : always run coverage
16
+ # - auto : enable on CI or when CODECOV_TOKEN is set
17
+ # - off : never run coverage
18
+ COVERAGE ?= on
19
+
20
+ .PHONY: help install dev test run clean build push release version-current bump bump-patch bump-minor bump-major menu _bootstrap-dev
21
+
22
+ help: ## Show help
23
+ @grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
24
+
25
+ install: ## Install package editable
26
+ $(PY) scripts/install.py
27
+
28
+ dev: ## Install package with dev extras
29
+ $(PY) scripts/dev.py
30
+
31
+ _bootstrap-dev:
32
+ @if [ "$(SKIP_BOOTSTRAP)" = "1" ]; then \
33
+ echo "[bootstrap] Skipping dev dependency bootstrap (SKIP_BOOTSTRAP=1)"; \
34
+ else \
35
+ if ! command -v ruff >/dev/null 2>&1 || ! command -v pyright >/dev/null 2>&1 || ! python -c "import pytest" >/dev/null 2>&1; then \
36
+ echo "[bootstrap] Installing dev dependencies via '$(PIP) install -e .[dev]'"; \
37
+ $(PIP) install -e .[dev]; \
38
+ else \
39
+ echo "[bootstrap] Dev tools present"; \
40
+ fi; \
41
+ if ! python -c "import sqlite3" >/dev/null 2>&1; then \
42
+ echo "[bootstrap] sqlite3 stdlib module not available; installing pysqlite3-binary"; \
43
+ $(PIP) install pysqlite3-binary || true; \
44
+ fi; \
45
+ fi
46
+
47
+ test: _bootstrap-dev ## Lint, type-check, run tests with coverage, upload to Codecov
48
+ $(PY) scripts/test.py --coverage=$(COVERAGE)
49
+
50
+ run: ## Run module CLI (requires dev install or src on PYTHONPATH)
51
+ $(PY) scripts/run_cli.py -- --help || true
52
+
53
+ version-current: ## Print current version from pyproject.toml
54
+ $(PY) scripts/version_current.py
55
+
56
+ bump: ## Bump version: VERSION=X.Y.Z or PART=major|minor|patch (default: patch); updates pyproject.toml and CHANGELOG.md
57
+ @set -e; \
58
+ if [ -n "$(VERSION)" ]; then \
59
+ $(PY) scripts/bump.py --version "$(VERSION)"; \
60
+ else \
61
+ $(PY) scripts/bump.py --part "$(PART)"; \
62
+ fi
63
+
64
+ bump-patch: ## Bump patch version (X.Y.Z -> X.Y.(Z+1))
65
+ $(PY) scripts/bump_patch.py
66
+
67
+ bump-minor: ## Bump minor version (X.Y.Z -> X.(Y+1).0)
68
+ $(PY) scripts/bump_minor.py
69
+
70
+ bump-major: ## Bump major version ((X+1).0.0)
71
+ $(PY) scripts/bump_major.py
72
+
73
+ clean: ## Remove caches, build artifacts, and coverage
74
+ rm -rf \
75
+ .pytest_cache \
76
+ .ruff_cache \
77
+ .pyright \
78
+ .mypy_cache \
79
+ .tox .nox \
80
+ .eggs *.egg-info \
81
+ build dist \
82
+ htmlcov .coverage coverage.xml \
83
+ codecov.sh \
84
+ .cache \
85
+ result
86
+
87
+ push: ## Commit all changes once and push to GitHub (no CI monitoring)
88
+ $(PY) scripts/push.py --remote=$(REMOTE)
89
+
90
+ build: ## Build wheel/sdist and attempt conda, brew, and nix builds (auto-installs tools if missing)
91
+ $(PY) scripts/build.py
92
+
93
+
94
+ release: ## Create and push tag vX.Y.Z from pyproject, create GitHub release (if gh present), then sync packaging
95
+ $(PY) scripts/release.py --remote=$(REMOTE)
96
+
97
+ menu: ## Interactive TUI menu
98
+ $(PY) -u scripts/menu.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.3
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,77 @@ 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
+
148
+ #### Target Parameters (env vars)
149
+
150
+ - Global
151
+ - `PY` (default: `python3`) — Python interpreter used to run scripts
152
+ - `PIP` (default: `pip`) — pip executable used by bootstrap/install
153
+
154
+ - `install`
155
+ - No specific parameters (respects `PY`, `PIP`).
156
+
157
+ - `dev`
158
+ - No specific parameters (respects `PY`, `PIP`).
159
+
160
+ - `test`
161
+ - `COVERAGE=on|auto|off` (default: `on`) — controls pytest coverage run and Codecov upload
162
+ - `SKIP_BOOTSTRAP=1` — skip auto-install of dev tools if missing
163
+ - `TEST_VERBOSE=1` — echo each command executed by the test harness
164
+ - Also respects `CODECOV_TOKEN` when needed for uploads
165
+
166
+ - `run`
167
+ - No parameters via `make` (always shows `--help`). For custom args: `python scripts/run_cli.py -- <args>`.
168
+
169
+ - `version-current`
170
+ - No parameters
171
+
172
+ - `bump`
173
+ - `VERSION=X.Y.Z` — explicit target version
174
+ - `PART=major|minor|patch` — semantic part to bump (default if `VERSION` not set: `patch`)
175
+ - Examples:
176
+ - `make bump VERSION=1.0.2`
177
+ - `make bump PART=minor`
178
+
179
+ - `bump-patch` / `bump-minor` / `bump-major`
180
+ - No parameters; shorthand for `make bump PART=...`
181
+
182
+ - `clean`
183
+ - No parameters
184
+
185
+ - `push`
186
+ - `REMOTE=<name>` (default: `origin`) — git remote to push to
187
+
188
+ - `build`
189
+ - No parameters via `make`. Advanced: use the script directly, e.g. `python scripts/build.py --no-conda --no-nix`.
190
+
191
+ - `release`
192
+ - `REMOTE=<name>` (default: `origin`) — git remote to push to
193
+ - Advanced (via script): `python scripts/release.py --retries 5 --retry-wait 3.0`
194
+
195
+ ### Interactive Menu (Textual)
196
+
197
+ `make menu` launches a colorful terminal UI (powered by `textual`) to browse targets, edit parameters, and run them with live output.
198
+
199
+ Install dev extras if you haven’t:
200
+
201
+ ```bash
202
+ pip install -e .[dev]
203
+ ```
204
+
205
+ Run the menu:
206
+
207
+ ```bash
208
+ make menu
209
+ ```
145
210
 
146
211
  #### Target Details
147
212
 
@@ -220,7 +285,7 @@ COVERAGE=on make test # force coverage and generate coverage.xml/codecov.
220
285
  - Homebrew: updates the source URL tag to `vX.Y.Z` and sets `depends_on "python@X.Y"` to match `requires-python`.
221
286
  - Nix: updates the package `version`, example `rev = "vX.Y.Z"`, and switches `pkgs.pythonXYZPackages` / `pkgs.pythonXYZ` to match the minimum Python version from `requires-python`.
222
287
 
223
- - To run just the sync without bumping versions: `python tools/bump_version.py --sync-packaging`.
288
+ - To run just the sync without bumping versions: `python scripts/bump_version.py --sync-packaging`.
224
289
 
225
290
  - On release tags (`v*.*.*`), CI validates that packaging files are consistent with `pyproject.toml` and will fail if they drift.
226
291
 
@@ -106,13 +106,77 @@ 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
+
118
+ #### Target Parameters (env vars)
119
+
120
+ - Global
121
+ - `PY` (default: `python3`) — Python interpreter used to run scripts
122
+ - `PIP` (default: `pip`) — pip executable used by bootstrap/install
123
+
124
+ - `install`
125
+ - No specific parameters (respects `PY`, `PIP`).
126
+
127
+ - `dev`
128
+ - No specific parameters (respects `PY`, `PIP`).
129
+
130
+ - `test`
131
+ - `COVERAGE=on|auto|off` (default: `on`) — controls pytest coverage run and Codecov upload
132
+ - `SKIP_BOOTSTRAP=1` — skip auto-install of dev tools if missing
133
+ - `TEST_VERBOSE=1` — echo each command executed by the test harness
134
+ - Also respects `CODECOV_TOKEN` when needed for uploads
135
+
136
+ - `run`
137
+ - No parameters via `make` (always shows `--help`). For custom args: `python scripts/run_cli.py -- <args>`.
138
+
139
+ - `version-current`
140
+ - No parameters
141
+
142
+ - `bump`
143
+ - `VERSION=X.Y.Z` — explicit target version
144
+ - `PART=major|minor|patch` — semantic part to bump (default if `VERSION` not set: `patch`)
145
+ - Examples:
146
+ - `make bump VERSION=1.0.2`
147
+ - `make bump PART=minor`
148
+
149
+ - `bump-patch` / `bump-minor` / `bump-major`
150
+ - No parameters; shorthand for `make bump PART=...`
151
+
152
+ - `clean`
153
+ - No parameters
154
+
155
+ - `push`
156
+ - `REMOTE=<name>` (default: `origin`) — git remote to push to
157
+
158
+ - `build`
159
+ - No parameters via `make`. Advanced: use the script directly, e.g. `python scripts/build.py --no-conda --no-nix`.
160
+
161
+ - `release`
162
+ - `REMOTE=<name>` (default: `origin`) — git remote to push to
163
+ - Advanced (via script): `python scripts/release.py --retries 5 --retry-wait 3.0`
164
+
165
+ ### Interactive Menu (Textual)
166
+
167
+ `make menu` launches a colorful terminal UI (powered by `textual`) to browse targets, edit parameters, and run them with live output.
168
+
169
+ Install dev extras if you haven’t:
170
+
171
+ ```bash
172
+ pip install -e .[dev]
173
+ ```
174
+
175
+ Run the menu:
176
+
177
+ ```bash
178
+ make menu
179
+ ```
116
180
 
117
181
  #### Target Details
118
182
 
@@ -191,7 +255,7 @@ COVERAGE=on make test # force coverage and generate coverage.xml/codecov.
191
255
  - Homebrew: updates the source URL tag to `vX.Y.Z` and sets `depends_on "python@X.Y"` to match `requires-python`.
192
256
  - Nix: updates the package `version`, example `rev = "vX.Y.Z"`, and switches `pkgs.pythonXYZPackages` / `pkgs.pythonXYZ` to match the minimum Python version from `requires-python`.
193
257
 
194
- - To run just the sync without bumping versions: `python tools/bump_version.py --sync-packaging`.
258
+ - To run just the sync without bumping versions: `python scripts/bump_version.py --sync-packaging`.
195
259
 
196
260
  - On release tags (`v*.*.*`), CI validates that packaging files are consistent with `pyproject.toml` and will fail if they drift.
197
261
 
@@ -20,9 +20,9 @@ ignore:
20
20
  - "tests/**"
21
21
  - "packaging/**"
22
22
  - "**/__init__conf__.py"
23
+ - "scripts/**"
23
24
 
24
25
  comment:
25
26
  layout: "reach, diff, flags, files"
26
27
  behavior: default
27
28
  require_changes: false
28
-
@@ -9,13 +9,13 @@ This directory contains packaging files and an automated sync flow so they stay
9
9
  - Or semantic part: `make bump PART=patch|minor|major`
10
10
  - To only synchronize packaging to the current `pyproject.toml` without changing versions:
11
11
  ```bash
12
- python3 tools/bump_version.py --sync-packaging
12
+ python3 scripts/bump_version.py --sync-packaging
13
13
  ```
14
14
  - `make test` runs the same sync step up-front to keep packaging up to date.
15
15
 
16
16
  ## What auto-sync updates
17
17
 
18
- The script `tools/bump_version.py` updates the following when network access is available:
18
+ The script `scripts/bump_version.py` updates the following when network access is available:
19
19
 
20
20
  1) Conda recipe (`packaging/conda/recipe/meta.yaml`)
21
21
  - Sets Jinja version to the current `pyproject.toml` version.
@@ -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.3.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.3" %}
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.3";
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.3";
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.3"
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,9 @@ target-version = "py313"
59
60
 
60
61
  [tool.pyright]
61
62
  typeCheckingMode = "strict"
63
+ exclude = [
64
+ "scripts/menu.py",
65
+ ]
62
66
 
63
67
  [tool.pytest.ini_options]
64
68
  addopts = ["--doctest-modules"]
@@ -0,0 +1,2 @@
1
+ [analysis]
2
+ ignore = ["scripts/"]
@@ -0,0 +1,3 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = []
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+ import re
3
+ import shlex
4
+ import subprocess
5
+ import sys
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Mapping, Sequence
9
+ from subprocess import CompletedProcess
10
+
11
+
12
+ @dataclass(slots=True)
13
+ class RunResult:
14
+ code: int
15
+ out: str
16
+ err: str
17
+
18
+
19
+ def run(
20
+ cmd: Sequence[str] | str, *, check: bool = True, capture: bool = True, cwd: str | None = None, env: Mapping[str, str] | None = None, dry_run: bool = False
21
+ ) -> RunResult:
22
+ if isinstance(cmd, str):
23
+ display = cmd
24
+ shell = True
25
+ args: Sequence[str] | str = cmd
26
+ else:
27
+ display = " ".join(shlex.quote(p) for p in cmd)
28
+ shell = False
29
+ args = list(cmd)
30
+ if dry_run:
31
+ print(f"[dry-run] {display}")
32
+ return RunResult(0, "", "")
33
+ proc: CompletedProcess[str] = subprocess.run(
34
+ args,
35
+ shell=shell,
36
+ cwd=cwd,
37
+ env=env,
38
+ text=True,
39
+ capture_output=capture,
40
+ )
41
+ if check and proc.returncode != 0:
42
+ raise SystemExit(proc.returncode)
43
+ return RunResult(int(proc.returncode or 0), proc.stdout or "", proc.stderr or "")
44
+
45
+
46
+ def cmd_exists(name: str) -> bool:
47
+ return subprocess.call(["bash", "-lc", f"command -v {shlex.quote(name)} >/dev/null 2>&1"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
48
+
49
+
50
+ def read_version_from_pyproject(pyproject: Path = Path("pyproject.toml")) -> str:
51
+ try:
52
+ import tomllib # pyright: ignore[reportMissingImports, reportMissingTypeStubs]
53
+
54
+ data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
55
+ version = str(data.get("project", {}).get("version", ""))
56
+ if version:
57
+ return version
58
+ except Exception:
59
+ pass
60
+ m = re.search(r'(?m)^version\s*=\s*"([0-9]+(?:\.[0-9]+){2})"', pyproject.read_text(encoding="utf-8"))
61
+ return m.group(1) if m else ""
62
+
63
+
64
+ def ensure_clean_git_tree() -> None:
65
+ # unstaged or staged changes?
66
+ dirty = subprocess.call(["bash", "-lc", "! git diff --quiet || ! git diff --cached --quiet"], stdout=subprocess.DEVNULL)
67
+ if dirty == 0:
68
+ print("[release] Working tree not clean. Commit or stash changes first.", file=sys.stderr)
69
+ raise SystemExit(1)
70
+
71
+
72
+ def git_branch() -> str:
73
+ return run(["git", "rev-parse", "--abbrev-ref", "HEAD"], capture=True).out.strip()
74
+
75
+
76
+ def git_delete_tag(name: str, *, remote: str | None = None) -> None:
77
+ run(["git", "tag", "-d", name], check=False, capture=True)
78
+ if remote:
79
+ run(["git", "push", remote, f":refs/tags/{name}"], check=False)
80
+
81
+
82
+ def git_tag_exists(name: str) -> bool:
83
+ return subprocess.call(["bash", "-lc", f"git rev-parse -q --verify {shlex.quote('refs/tags/' + name)} >/dev/null"], stdout=subprocess.DEVNULL) == 0
84
+
85
+
86
+ def git_create_annotated_tag(name: str, message: str) -> None:
87
+ run(["git", "tag", "-a", name, "-m", message])
88
+
89
+
90
+ def git_push(remote: str, ref: str) -> None:
91
+ run(["git", "push", remote, ref])
92
+
93
+
94
+ def gh_available() -> bool:
95
+ return cmd_exists("gh")
96
+
97
+
98
+ def gh_release_exists(tag: str) -> bool:
99
+ return subprocess.call(["bash", "-lc", f"gh release view {shlex.quote(tag)} >/dev/null 2>&1"], stdout=subprocess.DEVNULL) == 0
100
+
101
+
102
+ def gh_release_create(tag: str, title: str, body: str) -> None:
103
+ run(["gh", "release", "create", tag, "-t", title, "-n", body], check=False)
104
+
105
+
106
+ def gh_release_edit(tag: str, title: str, body: str) -> None:
107
+ run(["gh", "release", "edit", tag, "-t", title, "-n", body], check=False)
108
+
109
+
110
+ def sync_packaging() -> None:
111
+ run([sys.executable, "scripts/bump_version.py", "--sync-packaging"], check=False)
112
+
113
+
114
+ def bootstrap_dev() -> None:
115
+ # Install dev deps if ruff/pyright/pytest are missing
116
+ if not (cmd_exists("ruff") and cmd_exists("pyright")):
117
+ print("[bootstrap] Installing dev dependencies via 'pip install -e .[dev]'")
118
+ run([sys.executable, "-m", "pip", "install", "-e", ".[dev]"])
119
+ # sqlite fallback (rare)
120
+ try:
121
+ from importlib import import_module
122
+
123
+ import_module("sqlite3")
124
+ except Exception:
125
+ run([sys.executable, "-m", "pip", "install", "pysqlite3-binary"], check=False)
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ import sys
5
+
6
+ from ._utils import cmd_exists, run
7
+
8
+
9
+ @click.command(help="Build wheel/sdist, optionally attempt conda/brew/nix builds if tools present")
10
+ @click.option("--conda/--no-conda", default=True, help="Attempt conda build if conda present")
11
+ @click.option("--brew/--no-brew", default=True, help="Attempt Homebrew build if brew present (macOS)")
12
+ @click.option("--nix/--no-nix", default=True, help="Attempt Nix build if nix present")
13
+ def main(conda: bool, brew: bool, nix: bool) -> None:
14
+ click.echo("[1/4] Building wheel/sdist via python -m build")
15
+ run([sys.executable, "-m", "build"]) # requires build in dev deps
16
+
17
+ click.echo("[2/4] Attempting conda-build")
18
+ if conda and cmd_exists("conda"):
19
+ run(["bash", "-lc", "CONDA_USE_LOCAL=1 conda build packaging/conda/recipe"], check=False)
20
+ else:
21
+ click.echo("[conda] skipping: conda not available or disabled")
22
+
23
+ click.echo("[3/4] Attempting Homebrew build/install from local formula")
24
+ if brew and cmd_exists("brew"):
25
+ run(["bash", "-lc", "brew install --build-from-source packaging/brew/Formula/lib-cli-exit-tools.rb"], check=False)
26
+ else:
27
+ click.echo("[brew] skipping: Homebrew not available or disabled")
28
+
29
+ click.echo("[4/4] Attempting Nix flake build")
30
+ if nix and cmd_exists("nix"):
31
+ run(["bash", "-lc", "nix build packaging/nix#default -L"], check=False)
32
+ else:
33
+ click.echo("[nix] skipping: nix not available or disabled")
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main()
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+ from scripts._utils import run # noqa: E402
9
+
10
+
11
+ @click.command(help="Bump version in pyproject.toml and CHANGELOG.md")
12
+ @click.option("--version", "version_", type=str, help="Explicit version X.Y.Z")
13
+ @click.option("--part", type=click.Choice(["major", "minor", "patch"]), default=None)
14
+ @click.option("--pyproject", type=click.Path(path_type=Path), default=Path("pyproject.toml"))
15
+ @click.option("--changelog", type=click.Path(path_type=Path), default=Path("CHANGELOG.md"))
16
+ def main(version_: str | None, part: str | None, pyproject: Path, changelog: Path) -> None:
17
+ args = [sys.executable, "scripts/bump_version.py"]
18
+ if version_:
19
+ args += ["--version", version_]
20
+ else:
21
+ args += ["--part", part or "patch"]
22
+ args += ["--pyproject", str(pyproject), "--changelog", str(changelog)]
23
+ run(args)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+ from scripts.bump import main as bump_main # noqa: E402
9
+
10
+
11
+ @click.command(help="Bump major version")
12
+ def main() -> None: # pragma: no cover
13
+ bump_main.main(standalone_mode=False, args=["--part", "major"]) # type: ignore[attr-defined]
14
+
15
+
16
+ if __name__ == "__main__":
17
+ main()