lib-cli-exit-tools 2.2.4__py3-none-any.whl → 2.3.0__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.
- lib_cli_exit_tools/__init__.py +1 -0
- lib_cli_exit_tools/__init__conf__.py +1 -1
- lib_cli_exit_tools/application/runner.py +20 -28
- lib_cli_exit_tools/cli/__init__.py +27 -0
- lib_cli_exit_tools/cli/commands.py +69 -0
- lib_cli_exit_tools/cli/group.py +123 -0
- lib_cli_exit_tools/cli/styling.py +111 -0
- lib_cli_exit_tools/core/configuration.py +20 -4
- lib_cli_exit_tools/core/exit_codes.py +2 -2
- lib_cli_exit_tools/lib_cli_exit_tools.py +2 -1
- {lib_cli_exit_tools-2.2.4.dist-info → lib_cli_exit_tools-2.3.0.dist-info}/METADATA +7 -7
- lib_cli_exit_tools-2.3.0.dist-info/RECORD +21 -0
- lib_cli_exit_tools/cli.py +0 -261
- lib_cli_exit_tools-2.2.4.dist-info/RECORD +0 -18
- {lib_cli_exit_tools-2.2.4.dist-info → lib_cli_exit_tools-2.3.0.dist-info}/WHEEL +0 -0
- {lib_cli_exit_tools-2.2.4.dist-info → lib_cli_exit_tools-2.3.0.dist-info}/entry_points.txt +0 -0
- {lib_cli_exit_tools-2.2.4.dist-info → lib_cli_exit_tools-2.3.0.dist-info}/licenses/LICENSE +0 -0
lib_cli_exit_tools/__init__.py
CHANGED
|
@@ -17,6 +17,7 @@ from . import lib_cli_exit_tools as _facade
|
|
|
17
17
|
# the facade module. Attributes are assigned explicitly so static type checkers
|
|
18
18
|
# understand the exports, while the debug assertion keeps this module aligned
|
|
19
19
|
# with the facade surface.
|
|
20
|
+
ExitCodeStyle = _facade.ExitCodeStyle
|
|
20
21
|
CliSignalError = _facade.CliSignalError
|
|
21
22
|
SigBreakInterrupt = _facade.SigBreakInterrupt
|
|
22
23
|
SigIntInterrupt = _facade.SigIntInterrupt
|
|
@@ -24,7 +24,7 @@ name = "lib_cli_exit_tools"
|
|
|
24
24
|
#: Human-readable summary shown in CLI help output.
|
|
25
25
|
title = "CLI exit handling helpers: clean signals, exit codes, and error printing"
|
|
26
26
|
#: Current release version pulled from ``pyproject.toml`` by automation.
|
|
27
|
-
version = "2.
|
|
27
|
+
version = "2.3.0"
|
|
28
28
|
#: Repository homepage presented to users.
|
|
29
29
|
homepage = "https://github.com/bitranox/lib_cli_exit_tools"
|
|
30
30
|
#: Author attribution surfaced in CLI output.
|
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import sys
|
|
22
22
|
from contextlib import contextmanager, nullcontext, suppress
|
|
23
|
-
from typing import Callable, ContextManager, Iterable, Iterator, Literal,
|
|
23
|
+
from typing import Callable, ContextManager, Iterable, Iterator, Literal, Protocol, Sequence, TextIO, TypedDict, cast
|
|
24
24
|
|
|
25
25
|
import rich_click as click
|
|
26
26
|
from rich_click import rich_click as rich_config
|
|
@@ -29,17 +29,12 @@ from rich.text import Text
|
|
|
29
29
|
from rich.traceback import Traceback
|
|
30
30
|
|
|
31
31
|
from ..adapters.signals import SignalSpec, default_signal_specs, install_signal_handlers
|
|
32
|
-
from ..core.configuration import config, config_overrides
|
|
32
|
+
from ..core.configuration import ExitCodeStyle, config, config_overrides
|
|
33
33
|
from ..core.exit_codes import get_system_exit_code
|
|
34
34
|
|
|
35
35
|
RichColorSystem = Literal["auto", "standard", "256", "truecolor", "windows"]
|
|
36
36
|
ExitResolver = Callable[[BaseException], int | None]
|
|
37
37
|
|
|
38
|
-
# Configuration field name constants to ensure type-safe access
|
|
39
|
-
# These correspond to fields in _Config dataclass (core/configuration.py)
|
|
40
|
-
_CONFIG_TRACEBACK = "traceback"
|
|
41
|
-
_CONFIG_TRACEBACK_FORCE_COLOR = "traceback_force_color"
|
|
42
|
-
|
|
43
38
|
|
|
44
39
|
class SessionOverrides(TypedDict, total=False):
|
|
45
40
|
"""Type-safe override mapping for cli_session configuration.
|
|
@@ -49,7 +44,7 @@ class SessionOverrides(TypedDict, total=False):
|
|
|
49
44
|
"""
|
|
50
45
|
|
|
51
46
|
traceback: bool
|
|
52
|
-
exit_code_style:
|
|
47
|
+
exit_code_style: ExitCodeStyle
|
|
53
48
|
broken_pipe_exit_code: int
|
|
54
49
|
traceback_force_color: bool
|
|
55
50
|
|
|
@@ -464,7 +459,7 @@ def cli_session(
|
|
|
464
459
|
*,
|
|
465
460
|
summary_limit: int = 500,
|
|
466
461
|
verbose_limit: int = 10_000,
|
|
467
|
-
overrides:
|
|
462
|
+
overrides: SessionOverrides | None = None,
|
|
468
463
|
restore: bool = True,
|
|
469
464
|
) -> Iterator[
|
|
470
465
|
Callable[
|
|
@@ -498,7 +493,7 @@ def cli_session(
|
|
|
498
493
|
verbose_limit:
|
|
499
494
|
Character budget used when tracebacks are enabled.
|
|
500
495
|
overrides:
|
|
501
|
-
Optional mapping
|
|
496
|
+
Optional :class:`SessionOverrides` mapping temporarily applied during
|
|
502
497
|
the session. When ``traceback`` is supplied and
|
|
503
498
|
``traceback_force_color`` is omitted, colour output is forced to align
|
|
504
499
|
with the verbose mode.
|
|
@@ -544,26 +539,29 @@ def cli_session(
|
|
|
544
539
|
yield _run
|
|
545
540
|
|
|
546
541
|
|
|
547
|
-
def _normalise_session_overrides(overrides:
|
|
542
|
+
def _normalise_session_overrides(overrides: SessionOverrides | None) -> SessionOverrides:
|
|
548
543
|
"""Prepare configuration overrides, forcing colour when verbose tracebacks are enabled.
|
|
549
544
|
|
|
550
|
-
|
|
551
|
-
|
|
545
|
+
Why:
|
|
546
|
+
When traceback mode is enabled but force_color is not explicitly set,
|
|
547
|
+
automatically enable colour output to align with verbose mode.
|
|
548
|
+
Parameters:
|
|
549
|
+
overrides: Optional typed override mapping from the caller.
|
|
550
|
+
Returns:
|
|
551
|
+
Normalised override mapping with inferred defaults applied.
|
|
552
552
|
"""
|
|
553
553
|
if not overrides:
|
|
554
|
-
return
|
|
554
|
+
return SessionOverrides()
|
|
555
555
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
result: dict[str, object] = {**overrides}
|
|
560
|
-
result[_CONFIG_TRACEBACK_FORCE_COLOR] = bool(overrides[_CONFIG_TRACEBACK])
|
|
556
|
+
if "traceback" in overrides and "traceback_force_color" not in overrides:
|
|
557
|
+
result = SessionOverrides(**overrides)
|
|
558
|
+
result["traceback_force_color"] = bool(overrides["traceback"])
|
|
561
559
|
return result
|
|
562
560
|
|
|
563
561
|
return overrides
|
|
564
562
|
|
|
565
563
|
|
|
566
|
-
def _session_config_manager(applied:
|
|
564
|
+
def _session_config_manager(applied: SessionOverrides, restore: bool) -> ContextManager[object]:
|
|
567
565
|
"""Return the context manager used to apply session overrides."""
|
|
568
566
|
if restore:
|
|
569
567
|
return config_overrides(**applied)
|
|
@@ -573,17 +571,11 @@ def _session_config_manager(applied: Mapping[str, object], restore: bool) -> Con
|
|
|
573
571
|
|
|
574
572
|
|
|
575
573
|
@contextmanager
|
|
576
|
-
def _apply_overrides_without_restore(applied:
|
|
574
|
+
def _apply_overrides_without_restore(applied: SessionOverrides) -> Iterator[None]:
|
|
577
575
|
"""Apply overrides without restoring prior state on exit.
|
|
578
576
|
|
|
579
|
-
Safety Note:
|
|
580
|
-
The setattr calls here are type-safe because all field names in ``applied``
|
|
581
|
-
have been validated by ``_reject_unknown_fields`` in configuration.py
|
|
582
|
-
(line 114) before reaching this function. The config_overrides context
|
|
583
|
-
manager ensures only valid _Config field names can be used.
|
|
584
|
-
|
|
585
577
|
Parameters:
|
|
586
|
-
applied:
|
|
578
|
+
applied: Typed override mapping of configuration field names to values.
|
|
587
579
|
"""
|
|
588
580
|
for name, value in applied.items():
|
|
589
581
|
setattr(config, name, value)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Click-based CLI adapter for lib_cli_exit_tools.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Provide the end-user command-line surface while delegating error-handling
|
|
5
|
+
and exit-code logic to :mod:`lib_cli_exit_tools.lib_cli_exit_tools`.
|
|
6
|
+
Contents:
|
|
7
|
+
* :func:`cli` group exposing shared options.
|
|
8
|
+
* :func:`cli_info` subcommand reporting distribution metadata.
|
|
9
|
+
* :func:`cli_fail` subcommand triggering a deterministic failure for testing error paths.
|
|
10
|
+
* :func:`main` entry point used by console scripts and ``python -m``.
|
|
11
|
+
System Integration:
|
|
12
|
+
The CLI mutates :data:`lib_cli_exit_tools.config` based on the ``--traceback``
|
|
13
|
+
flag before handing execution off to :func:`lib_cli_exit_tools.run_cli`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
# --- Public surface re-exports (backward compatibility) ---
|
|
19
|
+
from .commands import cli_fail as cli_fail
|
|
20
|
+
from .commands import cli_info as cli_info
|
|
21
|
+
from .group import CliContextState as CliContextState
|
|
22
|
+
from .group import cli as cli
|
|
23
|
+
from .group import main as main
|
|
24
|
+
|
|
25
|
+
# --- Register subcommands with the root group ---
|
|
26
|
+
cli.add_command(cli_info)
|
|
27
|
+
cli.add_command(cli_fail)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Click subcommands for the lib_cli_exit_tools CLI.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Define individual CLI subcommands that expose library functionality to
|
|
5
|
+
end users (``info``, ``fail``).
|
|
6
|
+
Contents:
|
|
7
|
+
* :data:`CLICK_CONTEXT_SETTINGS` shared context settings for all commands.
|
|
8
|
+
* :func:`cli_info` subcommand reporting distribution metadata.
|
|
9
|
+
* :func:`cli_fail` subcommand triggering a deterministic failure.
|
|
10
|
+
System Integration:
|
|
11
|
+
Commands are registered with the root group in :mod:`lib_cli_exit_tools.cli`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import rich_click as click
|
|
17
|
+
|
|
18
|
+
from .. import __init__conf__
|
|
19
|
+
from .. import lib_cli_exit_tools
|
|
20
|
+
|
|
21
|
+
#: Help flag aliases applied to every Click command so documentation and CLI
|
|
22
|
+
#: behaviour stay consistent (`-h` mirrors `--help`).
|
|
23
|
+
#: Note: This uses dict literal syntax as required by Click framework API.
|
|
24
|
+
CLICK_CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@click.command("info", context_settings=CLICK_CONTEXT_SETTINGS)
|
|
28
|
+
def cli_info() -> None:
|
|
29
|
+
"""Display package metadata exported by :mod:`lib_cli_exit_tools.__init__conf__`.
|
|
30
|
+
|
|
31
|
+
Why:
|
|
32
|
+
Offer a zero-dependency way for users to confirm the installed version
|
|
33
|
+
and provenance of the CLI.
|
|
34
|
+
Side Effects:
|
|
35
|
+
Writes formatted metadata to stdout.
|
|
36
|
+
Examples:
|
|
37
|
+
>>> from click.testing import CliRunner
|
|
38
|
+
>>> from lib_cli_exit_tools.cli.group import cli
|
|
39
|
+
>>> runner = CliRunner()
|
|
40
|
+
>>> result = runner.invoke(cli, ["info"])
|
|
41
|
+
>>> "Info for" in result.output
|
|
42
|
+
True
|
|
43
|
+
"""
|
|
44
|
+
__init__conf__.print_info()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.command("fail", context_settings=CLICK_CONTEXT_SETTINGS)
|
|
48
|
+
def cli_fail() -> None:
|
|
49
|
+
"""Intentionally raise a failure to validate error handling.
|
|
50
|
+
|
|
51
|
+
Why:
|
|
52
|
+
Exercise the error-reporting path so engineers can confirm exit-code
|
|
53
|
+
translation and optional traceback behaviour without crafting custom
|
|
54
|
+
failing commands.
|
|
55
|
+
Side Effects:
|
|
56
|
+
Delegates to :func:`lib_cli_exit_tools.i_should_fail`, which raises
|
|
57
|
+
``RuntimeError`` for the caller to handle.
|
|
58
|
+
Examples:
|
|
59
|
+
>>> from click.testing import CliRunner
|
|
60
|
+
>>> from lib_cli_exit_tools.cli.group import cli
|
|
61
|
+
>>> runner = CliRunner()
|
|
62
|
+
>>> result = runner.invoke(cli, ["fail"])
|
|
63
|
+
>>> result.exit_code != 0
|
|
64
|
+
True
|
|
65
|
+
>>> "i should fail" in str(result.exception)
|
|
66
|
+
True
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
lib_cli_exit_tools.i_should_fail()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Root Click group, context state, and CLI entry point.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Define the top-level Click group with shared options (``--traceback``,
|
|
5
|
+
``--version``) and the :func:`main` entry point used by console scripts.
|
|
6
|
+
Contents:
|
|
7
|
+
* :class:`CliContextState` typed container for Click context state.
|
|
8
|
+
* :func:`cli` root Click group.
|
|
9
|
+
* :func:`main` entry point for console scripts and ``python -m``.
|
|
10
|
+
System Integration:
|
|
11
|
+
The CLI mutates :data:`lib_cli_exit_tools.config` based on the ``--traceback``
|
|
12
|
+
flag before handing execution off to :func:`lib_cli_exit_tools.run_cli`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Sequence
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
import rich_click as click
|
|
21
|
+
|
|
22
|
+
from .. import __init__conf__
|
|
23
|
+
from .. import lib_cli_exit_tools
|
|
24
|
+
from .commands import CLICK_CONTEXT_SETTINGS
|
|
25
|
+
from .styling import _temporary_rich_click_configuration # pyright: ignore[reportPrivateUsage]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class CliContextState:
|
|
30
|
+
"""Type-safe container for Click context object state.
|
|
31
|
+
|
|
32
|
+
Why:
|
|
33
|
+
Replaces untyped dict usage in ctx.obj to enforce type safety and
|
|
34
|
+
prevent string literal key access violations.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
traceback: Whether to show full Python tracebacks on errors.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
traceback: bool = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@click.group(help=__init__conf__.title, context_settings=CLICK_CONTEXT_SETTINGS)
|
|
44
|
+
@click.version_option(
|
|
45
|
+
version=__init__conf__.version,
|
|
46
|
+
prog_name=__init__conf__.shell_command,
|
|
47
|
+
message=f"{__init__conf__.shell_command} version {__init__conf__.version}",
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--traceback/--no-traceback",
|
|
51
|
+
is_flag=True,
|
|
52
|
+
default=False,
|
|
53
|
+
help="Show full Python traceback on errors",
|
|
54
|
+
)
|
|
55
|
+
@click.pass_context
|
|
56
|
+
def cli(ctx: click.Context, traceback: bool) -> None:
|
|
57
|
+
"""Root Click group that primes shared configuration state.
|
|
58
|
+
|
|
59
|
+
Why:
|
|
60
|
+
Accept a single ``--traceback`` flag that determines whether downstream
|
|
61
|
+
helpers emit stack traces.
|
|
62
|
+
Parameters:
|
|
63
|
+
ctx: Click context object for the current invocation.
|
|
64
|
+
traceback: When ``True`` enables traceback output for subsequent commands.
|
|
65
|
+
Side Effects:
|
|
66
|
+
Mutates ``ctx.obj`` and :data:`lib_cli_exit_tools.config.traceback`.
|
|
67
|
+
Examples:
|
|
68
|
+
>>> from click.testing import CliRunner
|
|
69
|
+
>>> runner = CliRunner()
|
|
70
|
+
>>> result = runner.invoke(cli, ["--help"])
|
|
71
|
+
>>> result.exit_code == 0
|
|
72
|
+
True
|
|
73
|
+
"""
|
|
74
|
+
_store_traceback_flag(ctx, traceback)
|
|
75
|
+
lib_cli_exit_tools.config.traceback = traceback
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _store_traceback_flag(ctx: click.Context, traceback: bool) -> None:
|
|
79
|
+
"""Store traceback flag in typed context state.
|
|
80
|
+
|
|
81
|
+
Parameters:
|
|
82
|
+
ctx: Click context object for the current invocation.
|
|
83
|
+
traceback: Whether to show full Python tracebacks on errors.
|
|
84
|
+
|
|
85
|
+
Side Effects:
|
|
86
|
+
Initializes ctx.obj as CliContextState if not already set and updates
|
|
87
|
+
the traceback field.
|
|
88
|
+
"""
|
|
89
|
+
ctx.ensure_object(CliContextState)
|
|
90
|
+
# Type narrowing: ctx.obj is guaranteed to be CliContextState after ensure_object
|
|
91
|
+
state = ctx.obj
|
|
92
|
+
if isinstance(state, CliContextState): # nosec B101 - type guard, not assertion
|
|
93
|
+
state.traceback = traceback
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
97
|
+
"""Run the CLI with :func:`lib_cli_exit_tools.run_cli` wiring.
|
|
98
|
+
|
|
99
|
+
Why:
|
|
100
|
+
Serve as the target for console scripts and ``python -m`` execution by
|
|
101
|
+
returning an integer exit code instead of exiting directly.
|
|
102
|
+
Parameters:
|
|
103
|
+
argv: Optional iterable of arguments passed to Click (without program name).
|
|
104
|
+
Returns:
|
|
105
|
+
Integer exit code from :func:`lib_cli_exit_tools.run_cli`.
|
|
106
|
+
Side Effects:
|
|
107
|
+
Delegates to Click and may write to stdout/stderr.
|
|
108
|
+
Examples:
|
|
109
|
+
>>> import contextlib, io
|
|
110
|
+
>>> buffer = io.StringIO()
|
|
111
|
+
>>> with contextlib.redirect_stdout(buffer):
|
|
112
|
+
... exit_code = main(["info"])
|
|
113
|
+
>>> exit_code
|
|
114
|
+
0
|
|
115
|
+
>>> "Info for" in buffer.getvalue()
|
|
116
|
+
True
|
|
117
|
+
"""
|
|
118
|
+
with _temporary_rich_click_configuration():
|
|
119
|
+
return lib_cli_exit_tools.run_cli(
|
|
120
|
+
cli,
|
|
121
|
+
argv=list(argv) if argv is not None else None,
|
|
122
|
+
prog_name=__init__conf__.shell_command,
|
|
123
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Rich-click styling adapter for terminal capability detection.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Detect terminal capabilities (TTY, encoding) and configure rich-click
|
|
5
|
+
global styling accordingly, falling back to ASCII-safe defaults when
|
|
6
|
+
the output stream cannot render Rich markup.
|
|
7
|
+
Contents:
|
|
8
|
+
* :class:`RichClickSnapshot` typed snapshot of rich-click globals.
|
|
9
|
+
* Stream inspection helpers for TTY and encoding detection.
|
|
10
|
+
* :func:`_temporary_rich_click_configuration` context manager.
|
|
11
|
+
System Integration:
|
|
12
|
+
Called by :func:`lib_cli_exit_tools.cli.group.main` before every CLI
|
|
13
|
+
invocation to ensure rich-click output matches terminal capabilities.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
from typing import Any, Iterator, TypedDict
|
|
20
|
+
|
|
21
|
+
import rich_click as click
|
|
22
|
+
from rich_click import rich_click as rich_config
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RichClickSnapshot(TypedDict):
|
|
26
|
+
"""Type-safe snapshot of rich-click global styling configuration.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
FORCE_TERMINAL: Whether to force terminal styling.
|
|
30
|
+
COLOR_SYSTEM: Color system to use (e.g., "auto", "256", "truecolor", None).
|
|
31
|
+
STYLE_OPTIONS_PANEL_BOX: Box style for options panel.
|
|
32
|
+
STYLE_COMMANDS_PANEL_BOX: Box style for commands panel.
|
|
33
|
+
STYLE_ERRORS_PANEL_BOX: Box style for errors panel.
|
|
34
|
+
|
|
35
|
+
Note:
|
|
36
|
+
Uses Any for field types to accommodate the dynamic nature of rich-click
|
|
37
|
+
configuration, which doesn't provide stable typing guarantees.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
FORCE_TERMINAL: Any
|
|
41
|
+
COLOR_SYSTEM: Any
|
|
42
|
+
STYLE_OPTIONS_PANEL_BOX: Any
|
|
43
|
+
STYLE_COMMANDS_PANEL_BOX: Any
|
|
44
|
+
STYLE_ERRORS_PANEL_BOX: Any
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _needs_plain_output(stream: object) -> bool:
|
|
48
|
+
"""Tell whether ``stream`` will honour Rich styling."""
|
|
49
|
+
|
|
50
|
+
return (not _stream_is_tty(stream)) or (not _stream_supports_utf(stream))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _stream_is_tty(stream: object) -> bool:
|
|
54
|
+
"""Return ``True`` when ``stream`` behaves like a TTY."""
|
|
55
|
+
checker = getattr(stream, "isatty", lambda: False)
|
|
56
|
+
try:
|
|
57
|
+
return bool(checker())
|
|
58
|
+
except Exception: # pragma: no cover - defensive shield
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _stream_supports_utf(stream: object) -> bool:
|
|
63
|
+
"""Tell whether ``stream`` reports a UTF-friendly encoding."""
|
|
64
|
+
encoding = (getattr(stream, "encoding", "") or "").lower()
|
|
65
|
+
return "utf" in encoding
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _prefer_ascii_layout() -> None:
|
|
69
|
+
"""Downgrade rich-click global styling to ASCII-friendly defaults."""
|
|
70
|
+
rich_config.FORCE_TERMINAL = False
|
|
71
|
+
rich_config.COLOR_SYSTEM = None
|
|
72
|
+
rich_config.STYLE_OPTIONS_PANEL_BOX = None
|
|
73
|
+
rich_config.STYLE_COMMANDS_PANEL_BOX = None
|
|
74
|
+
rich_config.STYLE_ERRORS_PANEL_BOX = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _snapshot_rich_click_options() -> RichClickSnapshot:
|
|
78
|
+
"""Capture rich-click global styling toggles for later restoration."""
|
|
79
|
+
|
|
80
|
+
return RichClickSnapshot(
|
|
81
|
+
FORCE_TERMINAL=getattr(rich_config, "FORCE_TERMINAL", None),
|
|
82
|
+
COLOR_SYSTEM=getattr(rich_config, "COLOR_SYSTEM", None),
|
|
83
|
+
STYLE_OPTIONS_PANEL_BOX=getattr(rich_config, "STYLE_OPTIONS_PANEL_BOX", None),
|
|
84
|
+
STYLE_COMMANDS_PANEL_BOX=getattr(rich_config, "STYLE_COMMANDS_PANEL_BOX", None),
|
|
85
|
+
STYLE_ERRORS_PANEL_BOX=getattr(rich_config, "STYLE_ERRORS_PANEL_BOX", None),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _restore_rich_click_options(snapshot: RichClickSnapshot) -> None:
|
|
90
|
+
"""Restore rich-click globals to the captured snapshot."""
|
|
91
|
+
|
|
92
|
+
rich_config.FORCE_TERMINAL = snapshot["FORCE_TERMINAL"]
|
|
93
|
+
rich_config.COLOR_SYSTEM = snapshot["COLOR_SYSTEM"]
|
|
94
|
+
rich_config.STYLE_OPTIONS_PANEL_BOX = snapshot["STYLE_OPTIONS_PANEL_BOX"]
|
|
95
|
+
rich_config.STYLE_COMMANDS_PANEL_BOX = snapshot["STYLE_COMMANDS_PANEL_BOX"]
|
|
96
|
+
rich_config.STYLE_ERRORS_PANEL_BOX = snapshot["STYLE_ERRORS_PANEL_BOX"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@contextmanager
|
|
100
|
+
def _temporary_rich_click_configuration() -> Iterator[None]: # pyright: ignore[reportUnusedFunction]
|
|
101
|
+
"""Apply plain-output safeguards and restore rich-click globals afterwards."""
|
|
102
|
+
|
|
103
|
+
snapshot = _snapshot_rich_click_options()
|
|
104
|
+
stdout_stream = click.get_text_stream("stdout")
|
|
105
|
+
stderr_stream = click.get_text_stream("stderr")
|
|
106
|
+
if _needs_plain_output(stdout_stream) and _needs_plain_output(stderr_stream):
|
|
107
|
+
_prefer_ascii_layout()
|
|
108
|
+
try:
|
|
109
|
+
yield
|
|
110
|
+
finally:
|
|
111
|
+
_restore_rich_click_options(snapshot)
|
|
@@ -23,9 +23,25 @@ from __future__ import annotations
|
|
|
23
23
|
from collections.abc import Iterator, Mapping
|
|
24
24
|
from contextlib import contextmanager
|
|
25
25
|
from dataclasses import dataclass, fields
|
|
26
|
-
from
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from typing import TypedDict
|
|
27
28
|
|
|
28
|
-
__all__ = ["_Config", "config", "config_overrides", "reset_config"]
|
|
29
|
+
__all__ = ["ExitCodeStyle", "_Config", "config", "config_overrides", "reset_config"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ExitCodeStyle(str, Enum):
|
|
33
|
+
"""Exit-code mapping strategy selector.
|
|
34
|
+
|
|
35
|
+
Why:
|
|
36
|
+
Replace raw string literals with a closed set of values so comparisons
|
|
37
|
+
are type-safe and spelling mistakes are caught at import time.
|
|
38
|
+
Members:
|
|
39
|
+
ERRNO: Use POSIX/Windows errno-based exit codes (default).
|
|
40
|
+
SYSEXITS: Use BSD sysexits.h conventions for shell-centric workflows.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
ERRNO = "errno"
|
|
44
|
+
SYSEXITS = "sysexits"
|
|
29
45
|
|
|
30
46
|
|
|
31
47
|
class ConfigSnapshot(TypedDict):
|
|
@@ -43,7 +59,7 @@ class ConfigSnapshot(TypedDict):
|
|
|
43
59
|
"""
|
|
44
60
|
|
|
45
61
|
traceback: bool
|
|
46
|
-
exit_code_style:
|
|
62
|
+
exit_code_style: ExitCodeStyle
|
|
47
63
|
broken_pipe_exit_code: int
|
|
48
64
|
traceback_force_color: bool
|
|
49
65
|
|
|
@@ -77,7 +93,7 @@ class _Config:
|
|
|
77
93
|
"""
|
|
78
94
|
|
|
79
95
|
traceback: bool = False
|
|
80
|
-
exit_code_style:
|
|
96
|
+
exit_code_style: ExitCodeStyle = ExitCodeStyle.ERRNO
|
|
81
97
|
broken_pipe_exit_code: int = 141
|
|
82
98
|
traceback_force_color: bool = False
|
|
83
99
|
|
|
@@ -19,7 +19,7 @@ import os
|
|
|
19
19
|
import subprocess # nosec B404 - imported for CalledProcessError type inspection
|
|
20
20
|
from typing import Callable, Iterable, Mapping
|
|
21
21
|
|
|
22
|
-
from .configuration import config
|
|
22
|
+
from .configuration import ExitCodeStyle, config
|
|
23
23
|
|
|
24
24
|
__all__ = ["get_system_exit_code"]
|
|
25
25
|
|
|
@@ -217,7 +217,7 @@ def _code_from_sysexits_mode(exc: BaseException) -> int | None:
|
|
|
217
217
|
Side Effects:
|
|
218
218
|
None.
|
|
219
219
|
"""
|
|
220
|
-
if config.exit_code_style !=
|
|
220
|
+
if config.exit_code_style != ExitCodeStyle.SYSEXITS:
|
|
221
221
|
return None
|
|
222
222
|
return _sysexits_resolved_code(exc)
|
|
223
223
|
|
|
@@ -37,10 +37,11 @@ from .application.runner import (
|
|
|
37
37
|
print_exception_message,
|
|
38
38
|
run_cli,
|
|
39
39
|
)
|
|
40
|
-
from .core.configuration import config, config_overrides, reset_config
|
|
40
|
+
from .core.configuration import ExitCodeStyle, config, config_overrides, reset_config
|
|
41
41
|
from .core.exit_codes import get_system_exit_code
|
|
42
42
|
|
|
43
43
|
__all__ = [
|
|
44
|
+
"ExitCodeStyle",
|
|
44
45
|
"config",
|
|
45
46
|
"get_system_exit_code",
|
|
46
47
|
"print_exception_message",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_cli_exit_tools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -16,22 +16,22 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
Requires-Python: >=3.10
|
|
19
|
-
Requires-Dist: rich-click>=1.9.
|
|
19
|
+
Requires-Dist: rich-click>=1.9.7
|
|
20
20
|
Provides-Extra: dev
|
|
21
21
|
Requires-Dist: bandit>=1.9.3; extra == 'dev'
|
|
22
22
|
Requires-Dist: build>=1.4.0; extra == 'dev'
|
|
23
23
|
Requires-Dist: codecov-cli>=11.2.6; extra == 'dev'
|
|
24
|
-
Requires-Dist: coverage[toml]>=7.13.
|
|
25
|
-
Requires-Dist: hypothesis>=6.151.
|
|
26
|
-
Requires-Dist: import-linter>=2.
|
|
24
|
+
Requires-Dist: coverage[toml]>=7.13.4; extra == 'dev'
|
|
25
|
+
Requires-Dist: hypothesis>=6.151.6; extra == 'dev'
|
|
26
|
+
Requires-Dist: import-linter>=2.10; extra == 'dev'
|
|
27
27
|
Requires-Dist: pip-audit>=2.10.0; extra == 'dev'
|
|
28
28
|
Requires-Dist: pyright[nodejs]>=1.1.408; extra == 'dev'
|
|
29
29
|
Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
|
|
30
30
|
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
31
31
|
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
32
32
|
Requires-Dist: rtoml>=0.13.0; extra == 'dev'
|
|
33
|
-
Requires-Dist: ruff>=0.
|
|
34
|
-
Requires-Dist: textual>=7.
|
|
33
|
+
Requires-Dist: ruff>=0.15.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: textual>=7.5.0; extra == 'dev'
|
|
35
35
|
Requires-Dist: twine>=6.2.0; extra == 'dev'
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
lib_cli_exit_tools/__init__.py,sha256=KzpZAMrc328V_nYm2GuTR0Sxyqh0XuKtlHP6dVCs2pc,1905
|
|
2
|
+
lib_cli_exit_tools/__init__conf__.py,sha256=tYrmAVN5mo_12V1_m_un7FtroeeZY0M9zHrPFXvnOdU,2521
|
|
3
|
+
lib_cli_exit_tools/__main__.py,sha256=wODJaNu_NyO7tknvlpPArq0MaDwDum8zqyb73-qshzo,257
|
|
4
|
+
lib_cli_exit_tools/lib_cli_exit_tools.py,sha256=fCLgJypLi6VHjmnh0McUn3CbhBhdzGYdDGcwcLokL4o,2561
|
|
5
|
+
lib_cli_exit_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
lib_cli_exit_tools/adapters/__init__.py,sha256=Jj8FCGY1lAPAduYSW3wTDh8ojpr9BZms7wKEMPq8pWE,504
|
|
7
|
+
lib_cli_exit_tools/adapters/signals.py,sha256=Q2D9Htv64nsT5n66cJFDDHRWVXJokaG77I-V8gTDMKg,5772
|
|
8
|
+
lib_cli_exit_tools/application/__init__.py,sha256=Yikej3OOOaAjWOvFimg1jUYrCUGu5d3-RBiiai4tHDM,566
|
|
9
|
+
lib_cli_exit_tools/application/runner.py,sha256=oTzARoieuN0JK7KRSzrMhKTFWF6GA8cxt1pK6YVjgVM,24413
|
|
10
|
+
lib_cli_exit_tools/cli/__init__.py,sha256=IVyU1DEgn2xBYfp1BBpund9nucUEPyOu7yt8DShu470,1094
|
|
11
|
+
lib_cli_exit_tools/cli/commands.py,sha256=WDF6hDsyczAb6cQp4fA6KMF1iqJQ1lzjv-RvIxq1itI,2418
|
|
12
|
+
lib_cli_exit_tools/cli/group.py,sha256=UFKb8VCkkQEB4CwIDckItrPHTRxSCddyLXKKr-2xU-w,4224
|
|
13
|
+
lib_cli_exit_tools/cli/styling.py,sha256=2W9-0Ig4X3RO8wVPukZEg2V_kBJfZKJANlawUOJAlhg,4236
|
|
14
|
+
lib_cli_exit_tools/core/__init__.py,sha256=aR7OIBWOQ1PsA33blOTgLrq5ffKDCuaMVBS5mbrDAZ8,531
|
|
15
|
+
lib_cli_exit_tools/core/configuration.py,sha256=ND0BBRBY7BkRbQWliWj-pp7pY1g8QULxgkeXfu2wfI8,6121
|
|
16
|
+
lib_cli_exit_tools/core/exit_codes.py,sha256=ukinlUROFKb6KmF_-8luxE1C2VJc6UwxIMMr0eB9xFg,12349
|
|
17
|
+
lib_cli_exit_tools-2.3.0.dist-info/METADATA,sha256=PQkASspOF7lCn8k3UG2y4lGdLJo_w-nHQWpoOirduC4,26234
|
|
18
|
+
lib_cli_exit_tools-2.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
19
|
+
lib_cli_exit_tools-2.3.0.dist-info/entry_points.txt,sha256=8R-_3UT0KcQ7HQ7idDx6rlG77GhHb0E2N5QnN41dj-M,161
|
|
20
|
+
lib_cli_exit_tools-2.3.0.dist-info/licenses/LICENSE,sha256=6s9SL2InRrwynPjJYiER7ONHBshbGj_yC5EBzfo7l9I,1072
|
|
21
|
+
lib_cli_exit_tools-2.3.0.dist-info/RECORD,,
|
lib_cli_exit_tools/cli.py
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
"""Click-based CLI adapter for lib_cli_exit_tools.
|
|
2
|
-
|
|
3
|
-
Purpose:
|
|
4
|
-
Provide the end-user command-line surface while delegating error-handling
|
|
5
|
-
and exit-code logic to :mod:`lib_cli_exit_tools.lib_cli_exit_tools`.
|
|
6
|
-
Contents:
|
|
7
|
-
* :func:`cli` group exposing shared options.
|
|
8
|
-
* :func:`cli_info` subcommand reporting distribution metadata.
|
|
9
|
-
* :func:`cli_fail` subcommand triggering a deterministic failure for testing error paths.
|
|
10
|
-
* :func:`main` entry point used by console scripts and ``python -m``.
|
|
11
|
-
System Integration:
|
|
12
|
-
The CLI mutates :data:`lib_cli_exit_tools.config` based on the ``--traceback``
|
|
13
|
-
flag before handing execution off to :func:`lib_cli_exit_tools.run_cli`.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
from contextlib import contextmanager
|
|
19
|
-
from dataclasses import dataclass
|
|
20
|
-
from typing import Any, Iterator, Sequence, TypedDict
|
|
21
|
-
|
|
22
|
-
import rich_click as click
|
|
23
|
-
from rich_click import rich_click as rich_config
|
|
24
|
-
|
|
25
|
-
from . import __init__conf__
|
|
26
|
-
from . import lib_cli_exit_tools
|
|
27
|
-
|
|
28
|
-
#: Help flag aliases applied to every Click command so documentation and CLI
|
|
29
|
-
#: behaviour stay consistent (`-h` mirrors `--help`).
|
|
30
|
-
#: Note: This uses dict literal syntax as required by Click framework API.
|
|
31
|
-
CLICK_CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class RichClickSnapshot(TypedDict):
|
|
35
|
-
"""Type-safe snapshot of rich-click global styling configuration.
|
|
36
|
-
|
|
37
|
-
Attributes:
|
|
38
|
-
FORCE_TERMINAL: Whether to force terminal styling.
|
|
39
|
-
COLOR_SYSTEM: Color system to use (e.g., "auto", "256", "truecolor", None).
|
|
40
|
-
STYLE_OPTIONS_PANEL_BOX: Box style for options panel.
|
|
41
|
-
STYLE_COMMANDS_PANEL_BOX: Box style for commands panel.
|
|
42
|
-
STYLE_ERRORS_PANEL_BOX: Box style for errors panel.
|
|
43
|
-
|
|
44
|
-
Note:
|
|
45
|
-
Uses Any for field types to accommodate the dynamic nature of rich-click
|
|
46
|
-
configuration, which doesn't provide stable typing guarantees.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
FORCE_TERMINAL: Any
|
|
50
|
-
COLOR_SYSTEM: Any
|
|
51
|
-
STYLE_OPTIONS_PANEL_BOX: Any
|
|
52
|
-
STYLE_COMMANDS_PANEL_BOX: Any
|
|
53
|
-
STYLE_ERRORS_PANEL_BOX: Any
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dataclass
|
|
57
|
-
class CliContextState:
|
|
58
|
-
"""Type-safe container for Click context object state.
|
|
59
|
-
|
|
60
|
-
Why:
|
|
61
|
-
Replaces untyped dict usage in ctx.obj to enforce type safety and
|
|
62
|
-
prevent string literal key access violations.
|
|
63
|
-
|
|
64
|
-
Attributes:
|
|
65
|
-
traceback: Whether to show full Python tracebacks on errors.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
traceback: bool = False
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _needs_plain_output(stream: object) -> bool:
|
|
72
|
-
"""Tell whether ``stream`` will honour Rich styling."""
|
|
73
|
-
|
|
74
|
-
return (not _stream_is_tty(stream)) or (not _stream_supports_utf(stream))
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _stream_is_tty(stream: object) -> bool:
|
|
78
|
-
"""Return ``True`` when ``stream`` behaves like a TTY."""
|
|
79
|
-
checker = getattr(stream, "isatty", lambda: False)
|
|
80
|
-
try:
|
|
81
|
-
return bool(checker())
|
|
82
|
-
except Exception: # pragma: no cover - defensive shield
|
|
83
|
-
return False
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def _stream_supports_utf(stream: object) -> bool:
|
|
87
|
-
"""Tell whether ``stream`` reports a UTF-friendly encoding."""
|
|
88
|
-
encoding = (getattr(stream, "encoding", "") or "").lower()
|
|
89
|
-
return "utf" in encoding
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _prefer_ascii_layout() -> None:
|
|
93
|
-
"""Downgrade rich-click global styling to ASCII-friendly defaults."""
|
|
94
|
-
rich_config.FORCE_TERMINAL = False
|
|
95
|
-
rich_config.COLOR_SYSTEM = None
|
|
96
|
-
rich_config.STYLE_OPTIONS_PANEL_BOX = None
|
|
97
|
-
rich_config.STYLE_COMMANDS_PANEL_BOX = None
|
|
98
|
-
rich_config.STYLE_ERRORS_PANEL_BOX = None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def _snapshot_rich_click_options() -> RichClickSnapshot:
|
|
102
|
-
"""Capture rich-click global styling toggles for later restoration."""
|
|
103
|
-
|
|
104
|
-
return RichClickSnapshot(
|
|
105
|
-
FORCE_TERMINAL=getattr(rich_config, "FORCE_TERMINAL", None),
|
|
106
|
-
COLOR_SYSTEM=getattr(rich_config, "COLOR_SYSTEM", None),
|
|
107
|
-
STYLE_OPTIONS_PANEL_BOX=getattr(rich_config, "STYLE_OPTIONS_PANEL_BOX", None),
|
|
108
|
-
STYLE_COMMANDS_PANEL_BOX=getattr(rich_config, "STYLE_COMMANDS_PANEL_BOX", None),
|
|
109
|
-
STYLE_ERRORS_PANEL_BOX=getattr(rich_config, "STYLE_ERRORS_PANEL_BOX", None),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _restore_rich_click_options(snapshot: RichClickSnapshot) -> None:
|
|
114
|
-
"""Restore rich-click globals to the captured snapshot."""
|
|
115
|
-
|
|
116
|
-
rich_config.FORCE_TERMINAL = snapshot["FORCE_TERMINAL"]
|
|
117
|
-
rich_config.COLOR_SYSTEM = snapshot["COLOR_SYSTEM"]
|
|
118
|
-
rich_config.STYLE_OPTIONS_PANEL_BOX = snapshot["STYLE_OPTIONS_PANEL_BOX"]
|
|
119
|
-
rich_config.STYLE_COMMANDS_PANEL_BOX = snapshot["STYLE_COMMANDS_PANEL_BOX"]
|
|
120
|
-
rich_config.STYLE_ERRORS_PANEL_BOX = snapshot["STYLE_ERRORS_PANEL_BOX"]
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
@contextmanager
|
|
124
|
-
def _temporary_rich_click_configuration() -> Iterator[None]:
|
|
125
|
-
"""Apply plain-output safeguards and restore rich-click globals afterwards."""
|
|
126
|
-
|
|
127
|
-
snapshot = _snapshot_rich_click_options()
|
|
128
|
-
stdout_stream = click.get_text_stream("stdout")
|
|
129
|
-
stderr_stream = click.get_text_stream("stderr")
|
|
130
|
-
if _needs_plain_output(stdout_stream) and _needs_plain_output(stderr_stream):
|
|
131
|
-
_prefer_ascii_layout()
|
|
132
|
-
try:
|
|
133
|
-
yield
|
|
134
|
-
finally:
|
|
135
|
-
_restore_rich_click_options(snapshot)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@click.group(help=__init__conf__.title, context_settings=CLICK_CONTEXT_SETTINGS)
|
|
139
|
-
@click.version_option(
|
|
140
|
-
version=__init__conf__.version,
|
|
141
|
-
prog_name=__init__conf__.shell_command,
|
|
142
|
-
message=f"{__init__conf__.shell_command} version {__init__conf__.version}",
|
|
143
|
-
)
|
|
144
|
-
@click.option(
|
|
145
|
-
"--traceback/--no-traceback",
|
|
146
|
-
is_flag=True,
|
|
147
|
-
default=False,
|
|
148
|
-
help="Show full Python traceback on errors",
|
|
149
|
-
)
|
|
150
|
-
@click.pass_context
|
|
151
|
-
def cli(ctx: click.Context, traceback: bool) -> None:
|
|
152
|
-
"""Root Click group that primes shared configuration state.
|
|
153
|
-
|
|
154
|
-
Why:
|
|
155
|
-
Accept a single ``--traceback`` flag that determines whether downstream
|
|
156
|
-
helpers emit stack traces.
|
|
157
|
-
Parameters:
|
|
158
|
-
ctx: Click context object for the current invocation.
|
|
159
|
-
traceback: When ``True`` enables traceback output for subsequent commands.
|
|
160
|
-
Side Effects:
|
|
161
|
-
Mutates ``ctx.obj`` and :data:`lib_cli_exit_tools.config.traceback`.
|
|
162
|
-
Examples:
|
|
163
|
-
>>> from click.testing import CliRunner
|
|
164
|
-
>>> runner = CliRunner()
|
|
165
|
-
>>> result = runner.invoke(cli, ["--help"])
|
|
166
|
-
>>> result.exit_code == 0
|
|
167
|
-
True
|
|
168
|
-
"""
|
|
169
|
-
_store_traceback_flag(ctx, traceback)
|
|
170
|
-
lib_cli_exit_tools.config.traceback = traceback
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _store_traceback_flag(ctx: click.Context, traceback: bool) -> None:
|
|
174
|
-
"""Store traceback flag in typed context state.
|
|
175
|
-
|
|
176
|
-
Parameters:
|
|
177
|
-
ctx: Click context object for the current invocation.
|
|
178
|
-
traceback: Whether to show full Python tracebacks on errors.
|
|
179
|
-
|
|
180
|
-
Side Effects:
|
|
181
|
-
Initializes ctx.obj as CliContextState if not already set and updates
|
|
182
|
-
the traceback field.
|
|
183
|
-
"""
|
|
184
|
-
ctx.ensure_object(CliContextState)
|
|
185
|
-
# Type narrowing: ctx.obj is guaranteed to be CliContextState after ensure_object
|
|
186
|
-
state = ctx.obj
|
|
187
|
-
if isinstance(state, CliContextState): # nosec B101 - type guard, not assertion
|
|
188
|
-
state.traceback = traceback
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
@cli.command("info", context_settings=CLICK_CONTEXT_SETTINGS)
|
|
192
|
-
def cli_info() -> None:
|
|
193
|
-
"""Display package metadata exported by :mod:`lib_cli_exit_tools.__init__conf__`.
|
|
194
|
-
|
|
195
|
-
Why:
|
|
196
|
-
Offer a zero-dependency way for users to confirm the installed version
|
|
197
|
-
and provenance of the CLI.
|
|
198
|
-
Side Effects:
|
|
199
|
-
Writes formatted metadata to stdout.
|
|
200
|
-
Examples:
|
|
201
|
-
>>> from click.testing import CliRunner
|
|
202
|
-
>>> runner = CliRunner()
|
|
203
|
-
>>> result = runner.invoke(cli, ["info"])
|
|
204
|
-
>>> "Info for" in result.output
|
|
205
|
-
True
|
|
206
|
-
"""
|
|
207
|
-
__init__conf__.print_info()
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
@cli.command("fail", context_settings=CLICK_CONTEXT_SETTINGS)
|
|
211
|
-
def cli_fail() -> None:
|
|
212
|
-
"""Intentionally raise a failure to validate error handling.
|
|
213
|
-
|
|
214
|
-
Why:
|
|
215
|
-
Exercise the error-reporting path so engineers can confirm exit-code
|
|
216
|
-
translation and optional traceback behaviour without crafting custom
|
|
217
|
-
failing commands.
|
|
218
|
-
Side Effects:
|
|
219
|
-
Delegates to :func:`lib_cli_exit_tools.i_should_fail`, which raises
|
|
220
|
-
``RuntimeError`` for the caller to handle.
|
|
221
|
-
Examples:
|
|
222
|
-
>>> from click.testing import CliRunner
|
|
223
|
-
>>> runner = CliRunner()
|
|
224
|
-
>>> result = runner.invoke(cli, ["fail"])
|
|
225
|
-
>>> result.exit_code != 0
|
|
226
|
-
True
|
|
227
|
-
>>> "i should fail" in result.output or "i should fail" in result.stderr
|
|
228
|
-
True
|
|
229
|
-
"""
|
|
230
|
-
|
|
231
|
-
lib_cli_exit_tools.i_should_fail()
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def main(argv: Sequence[str] | None = None) -> int:
|
|
235
|
-
"""Run the CLI with :func:`lib_cli_exit_tools.run_cli` wiring.
|
|
236
|
-
|
|
237
|
-
Why:
|
|
238
|
-
Serve as the target for console scripts and ``python -m`` execution by
|
|
239
|
-
returning an integer exit code instead of exiting directly.
|
|
240
|
-
Parameters:
|
|
241
|
-
argv: Optional iterable of arguments passed to Click (without program name).
|
|
242
|
-
Returns:
|
|
243
|
-
Integer exit code from :func:`lib_cli_exit_tools.run_cli`.
|
|
244
|
-
Side Effects:
|
|
245
|
-
Delegates to Click and may write to stdout/stderr.
|
|
246
|
-
Examples:
|
|
247
|
-
>>> import contextlib, io
|
|
248
|
-
>>> buffer = io.StringIO()
|
|
249
|
-
>>> with contextlib.redirect_stdout(buffer):
|
|
250
|
-
... exit_code = main(["info"])
|
|
251
|
-
>>> exit_code
|
|
252
|
-
0
|
|
253
|
-
>>> "Info for" in buffer.getvalue()
|
|
254
|
-
True
|
|
255
|
-
"""
|
|
256
|
-
with _temporary_rich_click_configuration():
|
|
257
|
-
return lib_cli_exit_tools.run_cli(
|
|
258
|
-
cli,
|
|
259
|
-
argv=list(argv) if argv is not None else None,
|
|
260
|
-
prog_name=__init__conf__.shell_command,
|
|
261
|
-
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
lib_cli_exit_tools/__init__.py,sha256=Z-VXcrmjZCgJZ8NW0UCVIc0HBOsjQicCbzgHj7r22eA,1867
|
|
2
|
-
lib_cli_exit_tools/__init__conf__.py,sha256=8b5wcSJwywaIvuqYoosuL3G0mpUqQ8kuVjpp67wdON4,2521
|
|
3
|
-
lib_cli_exit_tools/__main__.py,sha256=wODJaNu_NyO7tknvlpPArq0MaDwDum8zqyb73-qshzo,257
|
|
4
|
-
lib_cli_exit_tools/cli.py,sha256=k7LC08nVfse4i9RbsOGKTc9y-rrVbR1nOSKiwBvNxrE,9322
|
|
5
|
-
lib_cli_exit_tools/lib_cli_exit_tools.py,sha256=-_kjKGiWyMQ715KxOqrS6uA57SXOgJmMeowU7AmtC8A,2525
|
|
6
|
-
lib_cli_exit_tools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
lib_cli_exit_tools/adapters/__init__.py,sha256=Jj8FCGY1lAPAduYSW3wTDh8ojpr9BZms7wKEMPq8pWE,504
|
|
8
|
-
lib_cli_exit_tools/adapters/signals.py,sha256=Q2D9Htv64nsT5n66cJFDDHRWVXJokaG77I-V8gTDMKg,5772
|
|
9
|
-
lib_cli_exit_tools/application/__init__.py,sha256=Yikej3OOOaAjWOvFimg1jUYrCUGu5d3-RBiiai4tHDM,566
|
|
10
|
-
lib_cli_exit_tools/application/runner.py,sha256=AlCWX5o4VTRzsCom9EgnEK0iVCIZwyIdXtmqq5qegMM,24937
|
|
11
|
-
lib_cli_exit_tools/core/__init__.py,sha256=aR7OIBWOQ1PsA33blOTgLrq5ffKDCuaMVBS5mbrDAZ8,531
|
|
12
|
-
lib_cli_exit_tools/core/configuration.py,sha256=Ro6R2cmOIFhdOUogjK1VOxEJInx24Wgme0t-ucYJGEg,5658
|
|
13
|
-
lib_cli_exit_tools/core/exit_codes.py,sha256=nqx-e6h-ji-FiECPnDV8b0yrjcLPJyU-3xf4zhfRCBE,12322
|
|
14
|
-
lib_cli_exit_tools-2.2.4.dist-info/METADATA,sha256=RzJ0PBuOrRXwpWqnJkyjrd-mNlHxqi6jL2Nro9q2zoc,26234
|
|
15
|
-
lib_cli_exit_tools-2.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
16
|
-
lib_cli_exit_tools-2.2.4.dist-info/entry_points.txt,sha256=8R-_3UT0KcQ7HQ7idDx6rlG77GhHb0E2N5QnN41dj-M,161
|
|
17
|
-
lib_cli_exit_tools-2.2.4.dist-info/licenses/LICENSE,sha256=6s9SL2InRrwynPjJYiER7ONHBshbGj_yC5EBzfo7l9I,1072
|
|
18
|
-
lib_cli_exit_tools-2.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|