lib-cli-exit-tools 2.2.1__py3-none-any.whl

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.
@@ -0,0 +1,47 @@
1
+ """Public re-export surface for lib_cli_exit_tools helpers.
2
+
3
+ Purpose:
4
+ Provide a stable import path for consumers (`from lib_cli_exit_tools import run_cli`).
5
+ Contents:
6
+ Re-exports signal helpers, configuration, and CLI orchestration functions.
7
+ System Integration:
8
+ Keeps the package interface aligned with the module reference documented in
9
+ ``docs/system-design/module_reference.md`` while hiding implementation modules.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from . import lib_cli_exit_tools as _facade
15
+
16
+ # Re-export core helpers while keeping a single authoritative list of names in
17
+ # the facade module. Attributes are assigned explicitly so static type checkers
18
+ # understand the exports, while the debug assertion keeps this module aligned
19
+ # with the facade surface.
20
+ CliSignalError = _facade.CliSignalError
21
+ SigBreakInterrupt = _facade.SigBreakInterrupt
22
+ SigIntInterrupt = _facade.SigIntInterrupt
23
+ SigTermInterrupt = _facade.SigTermInterrupt
24
+ SignalSpec = _facade.SignalSpec
25
+ config = _facade.config
26
+ config_overrides = _facade.config_overrides
27
+ default_signal_specs = _facade.default_signal_specs
28
+ flush_streams = _facade.flush_streams
29
+ get_system_exit_code = _facade.get_system_exit_code
30
+ handle_cli_exception = _facade.handle_cli_exception
31
+ i_should_fail = _facade.i_should_fail
32
+ install_signal_handlers = _facade.install_signal_handlers
33
+ print_exception_message = _facade.print_exception_message
34
+ reset_config = _facade.reset_config
35
+ run_cli = _facade.run_cli
36
+ cli_session = _facade.cli_session
37
+
38
+ __all__ = list(_facade.PUBLIC_API) # pyright: ignore[reportUnsupportedDunderAll]
39
+
40
+ if __debug__:
41
+ exported = {name for name in __all__ if name in globals()}
42
+ if len(exported) != len(__all__):
43
+ missing = sorted(set(__all__) - exported)
44
+ raise ImportError(f"lib_cli_exit_tools exports out of sync: missing {missing}")
45
+ del exported
46
+
47
+ del _facade
@@ -0,0 +1,74 @@
1
+ """Static package metadata surfaced to CLI commands and documentation.
2
+
3
+ Purpose
4
+ -------
5
+ Expose the current project metadata as simple constants. These values are kept
6
+ in sync with ``pyproject.toml`` by development automation (tests, push
7
+ pipelines), so runtime code does not query packaging metadata.
8
+
9
+ Contents
10
+ --------
11
+ * Module-level constants describing the published package.
12
+ * :func:`print_info` rendering the constants for the CLI ``info`` command.
13
+
14
+ System Role
15
+ -----------
16
+ Lives in the adapters/platform layer; CLI transports import these constants to
17
+ present authoritative project information without invoking packaging APIs.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ #: Distribution name declared in ``pyproject.toml``.
23
+ name = "lib_cli_exit_tools"
24
+ #: Human-readable summary shown in CLI help output.
25
+ title = "CLI exit handling helpers: clean signals, exit codes, and error printing"
26
+ #: Current release version pulled from ``pyproject.toml`` by automation.
27
+ version = "2.2.1"
28
+ #: Repository homepage presented to users.
29
+ homepage = "https://github.com/bitranox/lib_cli_exit_tools"
30
+ #: Author attribution surfaced in CLI output.
31
+ author = "bitranox"
32
+ #: Contact email surfaced in CLI output.
33
+ author_email = "bitranox@gmail.com"
34
+ #: Console-script name published by the package.
35
+ shell_command = "lib-cli-exit-tools"
36
+
37
+ #: Vendor identifier for lib_layered_config paths (macOS/Windows)
38
+ LAYEREDCONF_VENDOR: str = "bitranox"
39
+ #: Application display name for lib_layered_config paths (macOS/Windows)
40
+ LAYEREDCONF_APP: str = "Lib Cli Exit Tools"
41
+ #: Configuration slug for lib_layered_config Linux paths and environment variables
42
+ LAYEREDCONF_SLUG: str = "lib-cli-exit-tools"
43
+
44
+
45
+ def print_info() -> None:
46
+ """Print the summarised metadata block used by the CLI ``info`` command.
47
+
48
+ Why
49
+ Provides a single, auditable rendering function so documentation and
50
+ CLI output always match the system design reference.
51
+
52
+ Side Effects
53
+ Writes to ``stdout``.
54
+
55
+ Examples
56
+ --------
57
+ >>> print_info() # doctest: +ELLIPSIS
58
+ Info for lib_cli_exit_tools:
59
+ ...
60
+ """
61
+
62
+ fields = [
63
+ ("name", name),
64
+ ("title", title),
65
+ ("version", version),
66
+ ("homepage", homepage),
67
+ ("author", author),
68
+ ("author_email", author_email),
69
+ ("shell_command", shell_command),
70
+ ]
71
+ pad = max(len(label) for label, _ in fields)
72
+ lines = [f"Info for {name}:", ""]
73
+ lines.extend(f" {label.ljust(pad)} = {value}" for label, value in fields)
74
+ print("\n".join(lines))
@@ -0,0 +1,13 @@
1
+ """Let ``python -m lib_cli_exit_tools`` behave like the console script."""
2
+
3
+ from . import cli
4
+
5
+
6
+ def main() -> int:
7
+ """Forward module execution to the Click entry point."""
8
+
9
+ return cli.main()
10
+
11
+
12
+ if __name__ == "__main__":
13
+ raise SystemExit(main())
@@ -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,178 @@
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 contextlib import ExitStack
20
+ from dataclasses import dataclass
21
+ from types import FrameType
22
+ from typing import Callable, Iterable, Sequence
23
+
24
+ __all__ = [
25
+ "CliSignalError",
26
+ "SigIntInterrupt",
27
+ "SigTermInterrupt",
28
+ "SigBreakInterrupt",
29
+ "SignalSpec",
30
+ "default_signal_specs",
31
+ "install_signal_handlers",
32
+ ]
33
+
34
+
35
+ class CliSignalError(RuntimeError):
36
+ """Base class for translating OS signals into structured CLI errors.
37
+
38
+ Why:
39
+ Provide a dedicated hierarchy so exit handlers can recognise signal-driven
40
+ interruptions and map them to deterministic exit codes.
41
+ Usage:
42
+ Raised automatically by handlers created via :func:`install_signal_handlers`.
43
+ """
44
+
45
+
46
+ class SigIntInterrupt(CliSignalError):
47
+ """Raised when the process receives ``SIGINT`` (Ctrl+C)."""
48
+
49
+
50
+ class SigTermInterrupt(CliSignalError):
51
+ """Raised when the process receives ``SIGTERM`` (termination request)."""
52
+
53
+
54
+ class SigBreakInterrupt(CliSignalError):
55
+ """Raised when the process receives ``SIGBREAK`` on Windows consoles."""
56
+
57
+
58
+ @dataclass(slots=True)
59
+ class SignalSpec:
60
+ """Describe how to translate a low-level signal into CLI-facing behaviour.
61
+
62
+ Fields:
63
+ signum: Numeric identifier registered with :mod:`signal`.
64
+ exception: Exception type raised by the generated handler.
65
+ message: User-facing text echoed to stderr when the signal fires.
66
+ exit_code: Numeric code returned to the operating system.
67
+ """
68
+
69
+ signum: int
70
+ exception: type[BaseException]
71
+ message: str
72
+ exit_code: int
73
+
74
+
75
+ _Handler = Callable[[int, FrameType | None], None]
76
+
77
+
78
+ def default_signal_specs(extra: Iterable[SignalSpec] | None = None) -> list[SignalSpec]:
79
+ """Build the default list of signal specifications for the host platform.
80
+
81
+ Why:
82
+ The application runner needs a predictable baseline of signals to
83
+ install without duplicating platform checks.
84
+ Parameters:
85
+ extra: Optional iterable of additional ``SignalSpec`` instances to
86
+ append for caller-specific behaviour.
87
+ Returns:
88
+ List of signal specifications tailored to the current interpreter.
89
+ """
90
+
91
+ specs: list[SignalSpec] = _standard_signal_specs()
92
+ if extra is not None:
93
+ specs.extend(extra)
94
+ return specs
95
+
96
+
97
+ def _make_raise_handler(exc_type: type[BaseException]) -> _Handler:
98
+ """Wrap ``exc_type`` in a signal-compatible callable."""
99
+
100
+ def _handler(signo: int, frame: FrameType | None) -> None: # pragma: no cover - just raises
101
+ raise exc_type()
102
+
103
+ return _handler
104
+
105
+
106
+ def install_signal_handlers(specs: Sequence[SignalSpec] | None = None) -> Callable[[], None]:
107
+ """Install signal handlers that re-raise as structured exceptions."""
108
+
109
+ active_specs = _choose_specs(specs)
110
+ stack = _register_handlers(active_specs)
111
+ return stack.close
112
+
113
+
114
+ def _choose_specs(specs: Sequence[SignalSpec] | None) -> list[SignalSpec]:
115
+ """Return a concrete list of signal specs, defaulting when ``None``."""
116
+ if specs is None:
117
+ return default_signal_specs()
118
+ return list(specs)
119
+
120
+
121
+ def _register_handlers(specs: Sequence[SignalSpec]) -> ExitStack:
122
+ """Register handlers for each ``SignalSpec`` and capture previous handlers."""
123
+
124
+ stack = ExitStack()
125
+ for spec in specs:
126
+ handler = _make_raise_handler(spec.exception)
127
+ try:
128
+ previous = signal.getsignal(spec.signum)
129
+ signal.signal(spec.signum, handler)
130
+ except (AttributeError, OSError, RuntimeError): # pragma: no cover - platform differences
131
+ continue
132
+ stack.callback(signal.signal, spec.signum, previous)
133
+ return stack
134
+
135
+
136
+ def _standard_signal_specs() -> list[SignalSpec]:
137
+ """Return the base set of signal specifications for all platforms."""
138
+ specs: list[SignalSpec] = [_sigint_spec()]
139
+ specs.extend(_optional_specs())
140
+ return specs
141
+
142
+
143
+ def _sigint_spec() -> SignalSpec:
144
+ """Return the ``SIGINT`` specification shared across all platforms."""
145
+ return SignalSpec(
146
+ signum=signal.SIGINT,
147
+ exception=SigIntInterrupt,
148
+ message="Aborted (SIGINT).",
149
+ exit_code=130,
150
+ )
151
+
152
+
153
+ def _optional_specs() -> Iterable[SignalSpec]:
154
+ """Yield platform-conditional signal specifications."""
155
+ yield from _maybe_sigterm_spec()
156
+ yield from _maybe_sigbreak_spec()
157
+
158
+
159
+ def _maybe_sigterm_spec() -> Iterable[SignalSpec]:
160
+ """Yield the ``SIGTERM`` specification when supported by the host."""
161
+ if hasattr(signal, "SIGTERM"):
162
+ yield SignalSpec(
163
+ signum=getattr(signal, "SIGTERM"),
164
+ exception=SigTermInterrupt,
165
+ message="Terminated (SIGTERM/SIGBREAK).",
166
+ exit_code=143,
167
+ )
168
+
169
+
170
+ def _maybe_sigbreak_spec() -> Iterable[SignalSpec]:
171
+ """Yield the ``SIGBREAK`` specification when running on Windows."""
172
+ if hasattr(signal, "SIGBREAK"):
173
+ yield SignalSpec(
174
+ signum=getattr(signal, "SIGBREAK"),
175
+ exception=SigBreakInterrupt,
176
+ message="Terminated (SIGBREAK).",
177
+ exit_code=149,
178
+ )
@@ -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] = []