lib-cli-exit-tools 1.3.1__tar.gz → 1.4.0__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.3.1 → lib_cli_exit_tools-1.4.0}/AGENTS.md +4 -24
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/CHANGELOG.md +6 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/PKG-INFO +6 -4
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/README.md +5 -3
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/docs/system-design/module_reference.md +50 -59
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/packaging/brew/Formula/lib-cli-exit-tools.rb +1 -1
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/packaging/conda/recipe/meta.yaml +1 -1
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/packaging/nix/flake.nix +1 -1
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/pyproject.toml +1 -1
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/adapters/__init__.py +16 -0
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/adapters/signals.py +177 -0
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/application/__init__.py +16 -0
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/application/runner.py +323 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/src/lib_cli_exit_tools/cli.py +1 -3
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/core/__init__.py +16 -0
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/core/configuration.py +58 -0
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/core/exit_codes.py +149 -0
- lib_cli_exit_tools-1.4.0/src/lib_cli_exit_tools/lib_cli_exit_tools.py +53 -0
- lib_cli_exit_tools-1.4.0/tests/adapters/test_signals.py +75 -0
- lib_cli_exit_tools-1.4.0/tests/application/test_runner.py +233 -0
- lib_cli_exit_tools-1.4.0/tests/cli/test_cli_entrypoints.py +103 -0
- lib_cli_exit_tools-1.4.0/tests/conftest.py +36 -0
- lib_cli_exit_tools-1.4.0/tests/core/test_exit_codes.py +151 -0
- lib_cli_exit_tools-1.3.1/src/lib_cli_exit_tools/lib_cli_exit_tools.py +0 -749
- lib_cli_exit_tools-1.3.1/tests/test_cli.py +0 -85
- lib_cli_exit_tools-1.3.1/tests/test_cli_extra.py +0 -32
- lib_cli_exit_tools-1.3.1/tests/test_exit_tools.py +0 -167
- lib_cli_exit_tools-1.3.1/tests/test_extend_cli.py +0 -111
- lib_cli_exit_tools-1.3.1/tests/test_lib_extra.py +0 -110
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/.env.example +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/.github/workflows/ci.yml +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/.github/workflows/codeql.yml +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/.github/workflows/release.yml +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/.gitignore +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/CONTRIBUTING.md +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/DEVELOPMENT.md +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/INSTALL.md +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/LICENSE +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/Makefile +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/codecov.yml +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/notebooks/Quickstart.ipynb +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/packaging/README.md +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/qlty.toml +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/__init__.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/_utils.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/build.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/bump.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/bump_major.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/bump_minor.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/bump_patch.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/bump_version.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/clean.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/dev.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/generate_nix_flake.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/install.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/menu.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/push.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/release.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/run_cli.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/test.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/scripts/version_current.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/src/lib_cli_exit_tools/__init__.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/src/lib_cli_exit_tools/__init__conf__.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/src/lib_cli_exit_tools/__main__.py +0 -0
- {lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/src/lib_cli_exit_tools/py.typed +0 -0
|
@@ -13,6 +13,8 @@ Review the system prompt files in `/media/srv-main/softdev/*/projects/softwarest
|
|
|
13
13
|
- python_structure_template.md
|
|
14
14
|
- self_documenting.md
|
|
15
15
|
- self_documenting_template.md
|
|
16
|
+
- python_jupyter_notebooks.md
|
|
17
|
+
- python_testing.md
|
|
16
18
|
|
|
17
19
|
always apply those Rules :
|
|
18
20
|
|
|
@@ -41,18 +43,6 @@ when writing or refracturing Python scripts, apply those Rules :
|
|
|
41
43
|
- `packaging/`: Conda, Homebrew, and Nix specs kept in sync via scripts.
|
|
42
44
|
- `tests/`: placeholder suite skipping until logging features exist.
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
## Build, Test, and Development Commands
|
|
46
|
-
|
|
47
|
-
- `make help` — list all targets with one‑line docs.
|
|
48
|
-
- `make test` — run ruff (lint + format check), pyright, pytest with coverage (enabled by default), and upload to Codecov (if configured via `.env`).
|
|
49
|
-
- Auto‑bootstrap: `make test` installs dev tools (`pip install -e .[dev]`) if linters/test deps are missing. Use `SKIP_BOOTSTRAP=1 make test` to disable.
|
|
50
|
-
- Coverage control: `COVERAGE=on|auto|off` (default `on` locally). Uses a unique `COVERAGE_FILE` each run to avoid DB locks.
|
|
51
|
-
- Before uploading to Codecov the harness creates an allow-empty commit (`test: auto commit before Codecov upload`) so the report attaches to a revision. Reset or amend if you do not want to keep it.
|
|
52
|
-
- `make build` — build Python wheel/sdist and attempt Conda/Homebrew/Nix builds (auto‑installs missing tools when needed).
|
|
53
|
-
- `make push` — runs the full `scripts/test.py` flow, prompts for/accepts a commit message (or `COMMIT_MESSAGE`), creates an allow-empty commit if needed, then pushes to the selected remote.
|
|
54
|
-
- `make clean` — remove caches, coverage, and build artifacts (includes `dist/` and `build/`).
|
|
55
|
-
|
|
56
46
|
### Versioning & Releases
|
|
57
47
|
|
|
58
48
|
- Single source of truth for the package version is `pyproject.toml` (`[project].version`).
|
|
@@ -84,13 +74,6 @@ when writing or refracturing Python scripts, apply those Rules :
|
|
|
84
74
|
|
|
85
75
|
## Coding Style & Naming Conventions
|
|
86
76
|
|
|
87
|
-
## Testing Guidelines
|
|
88
|
-
|
|
89
|
-
- Unit and integration-style tests live under `tests/`; keep them up to date when adding features.
|
|
90
|
-
- Extend coverage for new CLI or library behaviour (the suite exercises CLI commands, package metadata, and automation scripts).
|
|
91
|
-
- When adding functionality, replace or remove placeholders and ensure `make test` remains green.
|
|
92
|
-
- write good doctests which clearly show how the function works and what are the intensions. Use it as an enhancement of the inline documentation
|
|
93
|
-
|
|
94
77
|
## Commit & Pull Request Guidelines
|
|
95
78
|
|
|
96
79
|
## Architecture Overview
|
|
@@ -119,13 +102,10 @@ when writing or refracturing Python scripts, apply those Rules :
|
|
|
119
102
|
- run "make test" before any push to avoid lint/test breakage.
|
|
120
103
|
- after push, monitor errors in the github actions and try to correct the errors
|
|
121
104
|
|
|
122
|
-
## Jupyter Notebooks
|
|
123
|
-
use at least nbformat >=4.5
|
|
124
|
-
- "nbformat": 4,
|
|
125
|
-
- "nbformat_minor": 5
|
|
126
|
-
|
|
127
105
|
---
|
|
128
106
|
|
|
107
|
+
# ONLY ON USER REQEST !!!! :
|
|
108
|
+
|
|
129
109
|
# 📘 Documentation Review & Enhancement Prompt
|
|
130
110
|
|
|
131
111
|
You are a **senior software developer specializing in code documentation and system design clarity**. Your task is to review, correct, and enhance all documentation (external and inline) across the codebase with the following principles and workflow.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0] - 2025-09-26
|
|
4
|
+
|
|
5
|
+
- Reafracture, TestSuite
|
|
6
|
+
|
|
3
7
|
## [1.3.1] - 2025-09-26
|
|
4
8
|
|
|
5
9
|
- patch colored traceback
|
|
@@ -51,6 +55,8 @@
|
|
|
51
55
|
- Metadata lookup in `__init__conf__` resolved once and reused.
|
|
52
56
|
- Coverage gate relaxed to 80% and removed `# pragma: no cover` comments.
|
|
53
57
|
- Quickstart notebook defaults to install from GitHub; added cell IDs; last cell prints exit code example instead of exiting.
|
|
58
|
+
- Repartitioned the exit tooling into `core`, `adapters`, and `application` packages with `lib_cli_exit_tools` now acting as a facade to preserve import paths.
|
|
59
|
+
- `run_cli` accepts injectable `exception_handler` and `signal_installer` hooks; CLI rich-click configuration now runs during `main()` execution instead of at import time.
|
|
54
60
|
|
|
55
61
|
### Fixed
|
|
56
62
|
- Pyright 3.10 compatibility in `__init__conf__` by typing to a minimal metadata protocol; resolved unknown `.get` errors.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_cli_exit_tools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
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
|
|
@@ -155,7 +155,7 @@ Mutable dataclass-like singleton holding process-wide settings. Configure it dur
|
|
|
155
155
|
- `broken_pipe_exit_code` (`int`): Overrides the exit status for `BrokenPipeError` (default `141`).
|
|
156
156
|
- `traceback_force_color` (`bool`): Forces Rich-coloured tracebacks even when stderr is not a TTY.
|
|
157
157
|
|
|
158
|
-
### `run_cli(cli, argv=None, *, prog_name=None, signal_specs=None, install_signals=True) -> int`
|
|
158
|
+
### `run_cli(cli, argv=None, *, prog_name=None, signal_specs=None, install_signals=True, exception_handler=None, signal_installer=None) -> int`
|
|
159
159
|
Wrap a Click command or group so every invocation shares the same signal handling and exit-code policy. Returns the numeric exit code instead of exiting the process.
|
|
160
160
|
|
|
161
161
|
Parameters:
|
|
@@ -164,6 +164,8 @@ Parameters:
|
|
|
164
164
|
- `prog_name`: Override the program name shown in help/version output.
|
|
165
165
|
- `signal_specs`: Iterable of `SignalSpec` objects to customise signal handling; defaults to `default_signal_specs()`.
|
|
166
166
|
- `install_signals`: Set `False` when the host application already manages signal handlers.
|
|
167
|
+
- `exception_handler`: Optional callable receiving the raised exception and returning an exit code; defaults to ``handle_cli_exception``.
|
|
168
|
+
- `signal_installer`: Optional callable mirroring ``install_signal_handlers`` for embedding scenarios.
|
|
167
169
|
|
|
168
170
|
### `handle_cli_exception(exc, *, signal_specs=None, echo=None) -> int`
|
|
169
171
|
Translate exceptions raised by Click commands into deterministic exit codes, honouring configured signal mappings and traceback policy.
|
|
@@ -222,9 +224,9 @@ When `run_cli` executes your Click command it will:
|
|
|
222
224
|
2. Optionally install handlers that raise the exceptions above.
|
|
223
225
|
3. Execute the command with `standalone_mode=False`.
|
|
224
226
|
4. Funnel any exception through `handle_cli_exception`.
|
|
225
|
-
5. Restore prior signal handlers and flush streams before returning.
|
|
227
|
+
5. Restore prior signal handlers and flush streams before returning (or rely on any injected replacements).
|
|
226
228
|
|
|
227
|
-
This behaviour keeps CLI wiring consistent across projects embedding `lib_cli_exit_tools
|
|
229
|
+
This behaviour keeps CLI wiring consistent across projects embedding `lib_cli_exit_tools` while still allowing custom hooks when needed.
|
|
228
230
|
|
|
229
231
|
### Advanced CLI wiring
|
|
230
232
|
|
|
@@ -124,7 +124,7 @@ Mutable dataclass-like singleton holding process-wide settings. Configure it dur
|
|
|
124
124
|
- `broken_pipe_exit_code` (`int`): Overrides the exit status for `BrokenPipeError` (default `141`).
|
|
125
125
|
- `traceback_force_color` (`bool`): Forces Rich-coloured tracebacks even when stderr is not a TTY.
|
|
126
126
|
|
|
127
|
-
### `run_cli(cli, argv=None, *, prog_name=None, signal_specs=None, install_signals=True) -> int`
|
|
127
|
+
### `run_cli(cli, argv=None, *, prog_name=None, signal_specs=None, install_signals=True, exception_handler=None, signal_installer=None) -> int`
|
|
128
128
|
Wrap a Click command or group so every invocation shares the same signal handling and exit-code policy. Returns the numeric exit code instead of exiting the process.
|
|
129
129
|
|
|
130
130
|
Parameters:
|
|
@@ -133,6 +133,8 @@ Parameters:
|
|
|
133
133
|
- `prog_name`: Override the program name shown in help/version output.
|
|
134
134
|
- `signal_specs`: Iterable of `SignalSpec` objects to customise signal handling; defaults to `default_signal_specs()`.
|
|
135
135
|
- `install_signals`: Set `False` when the host application already manages signal handlers.
|
|
136
|
+
- `exception_handler`: Optional callable receiving the raised exception and returning an exit code; defaults to ``handle_cli_exception``.
|
|
137
|
+
- `signal_installer`: Optional callable mirroring ``install_signal_handlers`` for embedding scenarios.
|
|
136
138
|
|
|
137
139
|
### `handle_cli_exception(exc, *, signal_specs=None, echo=None) -> int`
|
|
138
140
|
Translate exceptions raised by Click commands into deterministic exit codes, honouring configured signal mappings and traceback policy.
|
|
@@ -191,9 +193,9 @@ When `run_cli` executes your Click command it will:
|
|
|
191
193
|
2. Optionally install handlers that raise the exceptions above.
|
|
192
194
|
3. Execute the command with `standalone_mode=False`.
|
|
193
195
|
4. Funnel any exception through `handle_cli_exception`.
|
|
194
|
-
5. Restore prior signal handlers and flush streams before returning.
|
|
196
|
+
5. Restore prior signal handlers and flush streams before returning (or rely on any injected replacements).
|
|
195
197
|
|
|
196
|
-
This behaviour keeps CLI wiring consistent across projects embedding `lib_cli_exit_tools
|
|
198
|
+
This behaviour keeps CLI wiring consistent across projects embedding `lib_cli_exit_tools` while still allowing custom hooks when needed.
|
|
197
199
|
|
|
198
200
|
### Advanced CLI wiring
|
|
199
201
|
|
{lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/docs/system-design/module_reference.md
RENAMED
|
@@ -10,6 +10,10 @@ Complete
|
|
|
10
10
|
**Related Files:**
|
|
11
11
|
|
|
12
12
|
* `src/lib_cli_exit_tools/lib_cli_exit_tools.py`
|
|
13
|
+
* `src/lib_cli_exit_tools/core/configuration.py`
|
|
14
|
+
* `src/lib_cli_exit_tools/core/exit_codes.py`
|
|
15
|
+
* `src/lib_cli_exit_tools/application/runner.py`
|
|
16
|
+
* `src/lib_cli_exit_tools/adapters/signals.py`
|
|
13
17
|
* `src/lib_cli_exit_tools/cli.py`
|
|
14
18
|
* `src/lib_cli_exit_tools/__init__conf__.py`
|
|
15
19
|
* `src/lib_cli_exit_tools/__init__.py`
|
|
@@ -42,8 +46,8 @@ Automations and downstream CLIs rely on `lib_cli_exit_tools` for deterministic e
|
|
|
42
46
|
1. Console script (or `python -m lib_cli_exit_tools`) resolves to `lib_cli_exit_tools.cli.main`.
|
|
43
47
|
2. The Click group stores global flags and mutates `lib_cli_exit_tools.config`.
|
|
44
48
|
3. `run_cli` optionally installs signal handlers, executes the requested Click command, and funnels exceptions into `handle_cli_exception`.
|
|
45
|
-
4. `handle_cli_exception` maps signals and errors to exit codes using `default_signal_specs
|
|
46
|
-
5. `print_exception_message` and `_print_output` render diagnostics when tracebacks are suppressed.
|
|
49
|
+
4. `handle_cli_exception` maps signals and errors to exit codes using `default_signal_specs` and `get_system_exit_code`.
|
|
50
|
+
5. `print_exception_message` and `_print_output` (within `application.runner`) render diagnostics when tracebacks are suppressed.
|
|
47
51
|
6. Packaging metadata from `__init__conf__` feeds CLI help/version output so docs and runtime stay synchronized.
|
|
48
52
|
|
|
49
53
|
**System Dependencies:** Standard library (`signal`, `subprocess`, `sys`, `importlib.metadata`) plus `click` and `rich-click` for CLI/TUI behavior.
|
|
@@ -53,80 +57,63 @@ Automations and downstream CLIs rely on `lib_cli_exit_tools` for deterministic e
|
|
|
53
57
|
## Core Components
|
|
54
58
|
|
|
55
59
|
### Module: lib_cli_exit_tools/lib_cli_exit_tools.py
|
|
56
|
-
* **Purpose:**
|
|
60
|
+
* **Purpose:** Provide a façade that re-exports configuration, signal helpers, and orchestration utilities so existing imports stay valid after the refactor.
|
|
57
61
|
* **Location:** `src/lib_cli_exit_tools/lib_cli_exit_tools.py`
|
|
62
|
+
* **Notes:** Contains no implementation logic; it simply aggregates symbols from the layered modules to minimise breaking changes.
|
|
63
|
+
|
|
64
|
+
### Module: lib_cli_exit_tools/core/configuration.py
|
|
65
|
+
* **Purpose:** Own the mutable runtime configuration shared across CLI executions.
|
|
66
|
+
* **Location:** `src/lib_cli_exit_tools/core/configuration.py`
|
|
58
67
|
|
|
59
68
|
#### Dataclass: `_Config`
|
|
60
|
-
* **
|
|
61
|
-
* **
|
|
62
|
-
* `traceback` (bool) — emit full tracebacks when `True`.
|
|
63
|
-
* `exit_code_style` (`"errno" | "sysexits"`) — choose mapping strategy.
|
|
64
|
-
* `broken_pipe_exit_code` (int) — exit status for `BrokenPipeError`.
|
|
65
|
-
* `traceback_force_color` (bool) — opt-in flag to force ANSI-coloured Rich tracebacks on non-TTY streams.
|
|
66
|
-
* **Notes:** Mutations apply process-wide; callers should restore defaults in tests.
|
|
69
|
+
* **Role:** Captures traceback, exit-code style, broken-pipe handling, and Rich colouring toggles.
|
|
70
|
+
* **Notes:** Mutations apply process-wide; tests must restore defaults to avoid leakage.
|
|
67
71
|
|
|
68
72
|
#### Constant: `config`
|
|
69
|
-
* **
|
|
70
|
-
* **Interactions:** Mutated by CLI
|
|
71
|
-
|
|
72
|
-
#### Exception Hierarchy: `CliSignalError`, `SigIntInterrupt`, `SigTermInterrupt`, `SigBreakInterrupt`
|
|
73
|
-
* **Purpose:** Represent signal-triggered interruptions so exit handlers can differentiate them from generic errors.
|
|
74
|
-
* **Notes:** Raised by generated signal handlers; mapped to exit codes 130 (SIGINT), 143 (SIGTERM), 149 (SIGBREAK).
|
|
73
|
+
* **Role:** Singleton instance consumed by adapters and orchestration code.
|
|
74
|
+
* **Interactions:** Mutated by the CLI (`--traceback`) and inspected by error handlers and exit-code mappers.
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
* **Purpose:**
|
|
78
|
-
* **
|
|
79
|
-
* **Usage:** Consumed by `install_signal_handlers`, `handle_cli_exception`.
|
|
76
|
+
### Module: lib_cli_exit_tools/core/exit_codes.py
|
|
77
|
+
* **Purpose:** Convert exceptions into deterministic OS exit statuses.
|
|
78
|
+
* **Location:** `src/lib_cli_exit_tools/core/exit_codes.py`
|
|
80
79
|
|
|
81
|
-
####
|
|
82
|
-
* **
|
|
83
|
-
* **
|
|
80
|
+
#### Function: `get_system_exit_code(exc: BaseException) -> int`
|
|
81
|
+
* **Role:** Mirrors platform-specific errno/winerror semantics or BSD sysexits based on `config.exit_code_style`.
|
|
82
|
+
* **Notes:** Handles `CalledProcessError`, `SystemExit`, and `BrokenPipeError` explicitly before falling back to errno tables.
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
* **Purpose:**
|
|
87
|
-
* **
|
|
84
|
+
### Module: lib_cli_exit_tools/adapters/signals.py
|
|
85
|
+
* **Purpose:** Translate operating-system signals into structured exceptions and reversible handlers.
|
|
86
|
+
* **Location:** `src/lib_cli_exit_tools/adapters/signals.py`
|
|
88
87
|
|
|
89
|
-
####
|
|
90
|
-
* **
|
|
91
|
-
* **Behavior:** Always includes SIGINT; adds SIGTERM/SIGBREAK when available.
|
|
88
|
+
#### Exceptions: `CliSignalError`, `SigIntInterrupt`, `SigTermInterrupt`, `SigBreakInterrupt`
|
|
89
|
+
* **Role:** Represent distinct signal pathways so exit handling can map them to stable codes (130/143/149).
|
|
92
90
|
|
|
93
|
-
####
|
|
94
|
-
* **
|
|
95
|
-
* **Usage:** Internal helper for `install_signal_handlers`.
|
|
96
|
-
|
|
97
|
-
#### Function: `install_signal_handlers(specs | None) -> Callable[[], None]`
|
|
98
|
-
* **Purpose:** Register handlers for provided signal specs and return a restoration callback.
|
|
99
|
-
* **Considerations:** Handlers are process-wide; callers must invoke the returned function to restore prior state.
|
|
100
|
-
|
|
101
|
-
#### Function: `handle_cli_exception(exc, *, signal_specs=None, echo=None) -> int`
|
|
102
|
-
* **Purpose:** Convert exceptions into deterministic exit codes, honoring configured signal mappings and traceback settings.
|
|
103
|
-
* **Behavior:** Detects signal exceptions, `BrokenPipeError`, `click.ClickException`, and `SystemExit`; otherwise defers to `print_exception_message` and `get_system_exit_code`.
|
|
104
|
-
|
|
105
|
-
#### Function: `run_cli(cli, argv=None, *, prog_name=None, signal_specs=None, install_signals=True) -> int`
|
|
106
|
-
* **Purpose:** Execute a Click command under shared signal/error handling, flushing streams before returning.
|
|
107
|
-
* **Behavior:** Optionally installs handlers, invokes `cli.main(...)`, funnels exceptions to `handle_cli_exception`, and restores handlers.
|
|
91
|
+
#### Dataclass: `SignalSpec`
|
|
92
|
+
* **Role:** Describe signal metadata (signum, exception, message, exit code) used by installers and handlers.
|
|
108
93
|
|
|
109
|
-
#### Function: `
|
|
110
|
-
* **
|
|
111
|
-
* **Behavior:** Supports subprocess return codes, `SystemExit`, `KeyboardInterrupt`, `BrokenPipeError`, and default fallbacks.
|
|
94
|
+
#### Function: `default_signal_specs(extra: Iterable[SignalSpec] | None = None) -> list[SignalSpec]`
|
|
95
|
+
* **Role:** Provide platform-aware defaults (always SIGINT; conditional SIGTERM/SIGBREAK) with optional extension points.
|
|
112
96
|
|
|
113
|
-
#### Function: `
|
|
114
|
-
* **
|
|
97
|
+
#### Function: `install_signal_handlers(specs: Sequence[SignalSpec] | None = None) -> Callable[[], None]`
|
|
98
|
+
* **Role:** Register handlers that raise the specified exceptions and return a restoration callback for clean-up.
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
* **Purpose:**
|
|
118
|
-
* **
|
|
100
|
+
### Module: lib_cli_exit_tools/application/runner.py
|
|
101
|
+
* **Purpose:** Orchestrate Click command execution with shared signal wiring, exception translation, and Rich-based diagnostics.
|
|
102
|
+
* **Location:** `src/lib_cli_exit_tools/application/runner.py`
|
|
119
103
|
|
|
120
|
-
####
|
|
121
|
-
* **
|
|
122
|
-
* **Behavior:** Flushes streams, surfaces captured subprocess output, renders highlighted tracebacks when enabled.
|
|
104
|
+
#### Protocol: `_Echo`
|
|
105
|
+
* **Role:** Structural type enabling dependency injection for stderr writers in tests.
|
|
123
106
|
|
|
124
|
-
####
|
|
125
|
-
* **
|
|
107
|
+
#### Utilities: `flush_streams`, `_build_console`, `_print_output`, `print_exception_message`
|
|
108
|
+
* **Role:** Manage stream flushing and Rich rendering when tracebacks are suppressed or forced.
|
|
126
109
|
|
|
127
|
-
#### Function: `
|
|
128
|
-
* **
|
|
110
|
+
#### Function: `handle_cli_exception(exc, *, signal_specs=None, echo=None) -> int`
|
|
111
|
+
* **Role:** Map exceptions to exit codes, emit signal messages, honour `config.traceback`, and delegate to `get_system_exit_code` when needed.
|
|
112
|
+
* **Notes:** Raises the original exception when traceback mode is enabled; otherwise trims output and prints captured subprocess streams.
|
|
129
113
|
|
|
114
|
+
#### Function: `run_cli(cli, argv=None, *, prog_name=None, signal_specs=None, install_signals=True, exception_handler=None, signal_installer=None) -> int`
|
|
115
|
+
* **Role:** Install signal handlers (unless disabled), execute the passed Click command, and funnel exceptions through an injectable handler before flushing streams.
|
|
116
|
+
* **Notes:** New `exception_handler` and `signal_installer` hooks support advanced embedding and unit testing scenarios.
|
|
130
117
|
### Module: lib_cli_exit_tools/cli.py
|
|
131
118
|
* **Purpose:** Provide the Click command group and subcommands that surface the exit tooling.
|
|
132
119
|
* **Location:** `src/lib_cli_exit_tools/cli.py`
|
|
@@ -187,6 +174,10 @@ Automations and downstream CLIs rely on `lib_cli_exit_tools` for deterministic e
|
|
|
187
174
|
* `install_signal_handlers` returns a restoration callback; callers must execute it (preferably in `finally`) to restore original handlers.
|
|
188
175
|
* SIGTERM and SIGBREAK specs are conditionally added based on platform capabilities.
|
|
189
176
|
|
|
177
|
+
**Extensibility:**
|
|
178
|
+
|
|
179
|
+
* `run_cli` exposes `exception_handler` and `signal_installer` hooks so embedders and unit tests can replace the default wiring without patching globals.
|
|
180
|
+
|
|
190
181
|
---
|
|
191
182
|
|
|
192
183
|
## Testing Approach
|
{lib_cli_exit_tools-1.3.1 → lib_cli_exit_tools-1.4.0}/packaging/brew/Formula/lib-cli-exit-tools.rb
RENAMED
|
@@ -3,7 +3,7 @@ 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.
|
|
6
|
+
url "https://github.com/bitranox/lib_cli_exit_tools/archive/refs/tags/v1.4.0.tar.gz"
|
|
7
7
|
k3cc316e7d0b09dd79e617cb3e91b14ee414b7e6695b108a326d9d3ccd08dd9"
|
|
8
8
|
license "MIT"
|
|
9
9
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Adapter layer for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Provide integration points with concrete frameworks (Click, Rich, signal
|
|
5
|
+
handlers) while delegating policy decisions to the core module.
|
|
6
|
+
Contents:
|
|
7
|
+
Modules added here will contain Click helpers, signal installers, and other
|
|
8
|
+
I/O-centric code paths.
|
|
9
|
+
System Integration:
|
|
10
|
+
Imported by the application layer to wire concrete behaviour without
|
|
11
|
+
violating dependency rules.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Signal handling adapters for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Translate operating-system signals into structured Python exceptions and
|
|
5
|
+
provide installation helpers that keep process-wide handlers reversible.
|
|
6
|
+
Contents:
|
|
7
|
+
* :class:`CliSignalError` hierarchy capturing supported interrupts.
|
|
8
|
+
* :class:`SignalSpec` dataclass describing signal→exception mappings.
|
|
9
|
+
* :func:`default_signal_specs` building platform-aware defaults.
|
|
10
|
+
* :func:`install_signal_handlers` installing reversible handlers.
|
|
11
|
+
System Integration:
|
|
12
|
+
The application runner leverages these helpers to provide consistent exit
|
|
13
|
+
codes across console entry points while allowing tests to inject fakes.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import signal
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from types import FrameType
|
|
21
|
+
from typing import Callable, Iterable, List, Sequence
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"CliSignalError",
|
|
25
|
+
"SigIntInterrupt",
|
|
26
|
+
"SigTermInterrupt",
|
|
27
|
+
"SigBreakInterrupt",
|
|
28
|
+
"SignalSpec",
|
|
29
|
+
"default_signal_specs",
|
|
30
|
+
"install_signal_handlers",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CliSignalError(RuntimeError):
|
|
35
|
+
"""Base class for translating OS signals into structured CLI errors.
|
|
36
|
+
|
|
37
|
+
Why:
|
|
38
|
+
Provide a dedicated hierarchy so exit handlers can recognise signal-driven
|
|
39
|
+
interruptions and map them to deterministic exit codes.
|
|
40
|
+
Usage:
|
|
41
|
+
Raised automatically by handlers created via :func:`install_signal_handlers`.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SigIntInterrupt(CliSignalError):
|
|
46
|
+
"""Raised when the process receives ``SIGINT`` (Ctrl+C)."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SigTermInterrupt(CliSignalError):
|
|
50
|
+
"""Raised when the process receives ``SIGTERM`` (termination request)."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SigBreakInterrupt(CliSignalError):
|
|
54
|
+
"""Raised when the process receives ``SIGBREAK`` on Windows consoles."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(slots=True)
|
|
58
|
+
class SignalSpec:
|
|
59
|
+
"""Describe how to translate a low-level signal into CLI-facing behaviour.
|
|
60
|
+
|
|
61
|
+
Fields:
|
|
62
|
+
signum: Numeric identifier registered with :mod:`signal`.
|
|
63
|
+
exception: Exception type raised by the generated handler.
|
|
64
|
+
message: User-facing text echoed to stderr when the signal fires.
|
|
65
|
+
exit_code: Numeric code returned to the operating system.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
signum: int
|
|
69
|
+
exception: type[BaseException]
|
|
70
|
+
message: str
|
|
71
|
+
exit_code: int
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_Handler = Callable[[int, FrameType | None], None]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def default_signal_specs(extra: Iterable[SignalSpec] | None = None) -> List[SignalSpec]:
|
|
78
|
+
"""Build the default list of signal specifications for the host platform.
|
|
79
|
+
|
|
80
|
+
Why:
|
|
81
|
+
Ensure every CLI managed by this project responds consistently to Ctrl+C
|
|
82
|
+
and termination signals without duplicating configuration.
|
|
83
|
+
Parameters:
|
|
84
|
+
extra: Optional iterable of additional :class:`SignalSpec` records that
|
|
85
|
+
extend the standard platform-aware set.
|
|
86
|
+
Returns:
|
|
87
|
+
Fresh list of :class:`SignalSpec` entries ready for
|
|
88
|
+
:func:`install_signal_handlers` or :func:`handle_cli_exception`.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
specs: List[SignalSpec] = [
|
|
92
|
+
SignalSpec(
|
|
93
|
+
signum=signal.SIGINT,
|
|
94
|
+
exception=SigIntInterrupt,
|
|
95
|
+
message="Aborted (SIGINT).",
|
|
96
|
+
exit_code=130,
|
|
97
|
+
)
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
if hasattr(signal, "SIGTERM"):
|
|
101
|
+
specs.append(
|
|
102
|
+
SignalSpec(
|
|
103
|
+
signum=getattr(signal, "SIGTERM"),
|
|
104
|
+
exception=SigTermInterrupt,
|
|
105
|
+
message="Terminated (SIGTERM/SIGBREAK).",
|
|
106
|
+
exit_code=143,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
if hasattr(signal, "SIGBREAK"):
|
|
110
|
+
specs.append(
|
|
111
|
+
SignalSpec(
|
|
112
|
+
signum=getattr(signal, "SIGBREAK"),
|
|
113
|
+
exception=SigBreakInterrupt,
|
|
114
|
+
message="Terminated (SIGBREAK).",
|
|
115
|
+
exit_code=149,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if extra is not None:
|
|
120
|
+
specs.extend(extra)
|
|
121
|
+
|
|
122
|
+
return specs
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _make_raise_handler(exc_type: type[BaseException]) -> _Handler:
|
|
126
|
+
"""Wrap ``exc_type`` in a signal-compatible callable.
|
|
127
|
+
|
|
128
|
+
Why:
|
|
129
|
+
``signal.signal`` expects handlers with a ``(signum, frame)`` signature.
|
|
130
|
+
The returned closure ignores those arguments and raises ``exc_type``
|
|
131
|
+
immediately so higher layers see a structured exception.
|
|
132
|
+
Parameters:
|
|
133
|
+
exc_type: Exception subclass to raise when the signal fires.
|
|
134
|
+
Returns:
|
|
135
|
+
Callable compatible with ``signal.signal`` registration.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def _handler(signo: int, frame: FrameType | None) -> None: # pragma: no cover - just raises
|
|
139
|
+
raise exc_type()
|
|
140
|
+
|
|
141
|
+
return _handler
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def install_signal_handlers(specs: Sequence[SignalSpec] | None = None) -> Callable[[], None]:
|
|
145
|
+
"""Install signal handlers that re-raise as structured exceptions.
|
|
146
|
+
|
|
147
|
+
Why:
|
|
148
|
+
Centralise signal wiring so CLI entry points can opt in with a single
|
|
149
|
+
call and reliably restore previous handlers afterwards.
|
|
150
|
+
Parameters:
|
|
151
|
+
specs: Iterable of :class:`SignalSpec` entries. When omitted the
|
|
152
|
+
platform-aware defaults from :func:`default_signal_specs` are used.
|
|
153
|
+
Returns:
|
|
154
|
+
Callable that restores the prior handlers. Invoke it in ``finally`` to
|
|
155
|
+
avoid leaking process-wide state into callers or tests.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
active_specs = list(default_signal_specs() if specs is None else specs)
|
|
159
|
+
previous: List[tuple[int, object]] = []
|
|
160
|
+
|
|
161
|
+
for spec in active_specs:
|
|
162
|
+
handler = _make_raise_handler(spec.exception)
|
|
163
|
+
try:
|
|
164
|
+
current = signal.getsignal(spec.signum)
|
|
165
|
+
signal.signal(spec.signum, handler)
|
|
166
|
+
previous.append((spec.signum, current))
|
|
167
|
+
except (AttributeError, OSError, RuntimeError): # pragma: no cover - platform differences
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
def restore() -> None:
|
|
171
|
+
for signum, prior in previous:
|
|
172
|
+
try:
|
|
173
|
+
signal.signal(signum, prior) # type: ignore[arg-type]
|
|
174
|
+
except Exception: # pragma: no cover - restore best-effort
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
return restore
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Application orchestration for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Coordinate core exit-code logic with adapter implementations (Click,
|
|
5
|
+
signal handlers) while exposing a small surface for CLI entry points.
|
|
6
|
+
Contents:
|
|
7
|
+
The `runner` module will host the refactored `run_cli` orchestration and any
|
|
8
|
+
supporting protocols.
|
|
9
|
+
System Integration:
|
|
10
|
+
Imports only from `lib_cli_exit_tools.core` and adapter packages, keeping the
|
|
11
|
+
dependency direction consistent with Clean Architecture guidelines.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
__all__: list[str] = []
|