Coverage for / home / runner / work / bijux-cli / bijux-cli / src / bijux_cli / cli / commands / repl.py: 100%
42 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 17:59 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-26 17:59 +0000
1# SPDX-License-Identifier: Apache-2.0
2# Copyright © 2025 Bijan Mousavi
4"""Implements the interactive Read-Eval-Print Loop (REPL) for the Bijux CLI.
6This module provides a rich, interactive shell for executing Bijux CLI commands.
7It enhances the user experience with features like persistent command history,
8context-aware tab-completion, and a colorized prompt. Users can chain multiple
9commands on a single line using semicolons. The REPL can also operate in a
10non-interactive mode to process commands piped from stdin.
12The REPL itself operates in a human-readable format. When executing commands,
13it respects global flags like format or quiet for those specific invocations.
15Exit Codes:
16 * `0`: The REPL session was exited cleanly (e.g., via `exit`, `quit`,
17 Ctrl+D, or a caught signal).
18 * `2`: An invalid flag was provided to the `repl` command itself
19 (e.g., an unsupported format).
20"""
22from __future__ import annotations
24import sys
26import typer
28from bijux_cli.cli.core.command import validate_common_flags
29from bijux_cli.cli.core.constants import (
30 OPT_FORMAT,
31 OPT_LOG_LEVEL,
32 OPT_PRETTY,
33 OPT_QUIET,
34)
35from bijux_cli.cli.core.help_text import (
36 HELP_FORMAT_HELP,
37 HELP_LOG_LEVEL,
38 HELP_NO_PRETTY,
39 HELP_QUIET,
40)
41from bijux_cli.cli.repl.execution import _run_piped as _exec_run_piped
42from bijux_cli.cli.repl.ui import register_signal_handlers
43from bijux_cli.core.enums import ErrorType
44from bijux_cli.core.exit_policy import ExitIntentError
45from bijux_cli.core.precedence import current_execution_policy, resolve_exit_intent
46from bijux_cli.core.runtime import AsyncTyper, run_command
48repl_app = AsyncTyper(
49 name="repl",
50 help="Starts an interactive shell with history and tab-completion.",
51 add_completion=False,
52)
55def _run_piped(repl_quiet: bool) -> None:
56 """Run the REPL in non-interactive mode."""
57 _exec_run_piped(repl_quiet)
60async def _run_interactive() -> None:
61 """Run the interactive REPL loop."""
62 from bijux_cli.cli.repl.ui import _run_interactive as _ui_run_interactive
64 await _ui_run_interactive()
67def _run_repl_session(*, quiet: bool, stdin_isatty: bool) -> None:
68 """Route to piped or interactive mode."""
69 interactive = stdin_isatty and not quiet
70 if not interactive:
71 _run_piped(quiet)
72 return
73 run_command(_run_interactive)
76@repl_app.callback(invoke_without_command=True)
77def main(
78 ctx: typer.Context,
79 quiet: bool = typer.Option(False, *OPT_QUIET, help=HELP_QUIET),
80 fmt: str = typer.Option("human", *OPT_FORMAT, help=HELP_FORMAT_HELP),
81 pretty: bool = typer.Option(True, OPT_PRETTY, help=HELP_NO_PRETTY),
82 log_level: str = typer.Option("info", *OPT_LOG_LEVEL, help=HELP_LOG_LEVEL),
83) -> None:
84 """Defines the entrypoint for the `bijux repl` command.
86 This function initializes the REPL environment. It validates flags, sets
87 up signal handlers for clean shutdown, and dispatches to either the
88 non-interactive (piped) mode or the interactive async prompt loop.
90 Args:
91 ctx (typer.Context): The Typer context for the CLI.
92 quiet (bool): If True, forces non-interactive mode and suppresses
93 prompts and command output.
94 fmt (str): The desired output format. Only "human" is supported for
95 the REPL itself.
96 pretty (bool): If True, enables pretty-printing for subcommands.
97 log_level (str): The requested logging level for subcommands.
99 Returns:
100 None:
101 """
102 if ctx.invoked_subcommand:
103 return
105 command = "repl"
106 policy = current_execution_policy()
107 effective_include_runtime = policy.include_runtime
108 quiet = policy.quiet
110 fmt_lower = fmt.strip().lower()
111 format_value = None
113 if fmt_lower != "human":
114 format_value = validate_common_flags(
115 fmt,
116 command,
117 policy.quiet,
118 include_runtime=effective_include_runtime,
119 log_level=policy.log_level,
120 )
121 intent = resolve_exit_intent(
122 message="REPL only supports human format.",
123 code=2,
124 failure="format",
125 command=command,
126 fmt=format_value,
127 quiet=policy.quiet,
128 include_runtime=effective_include_runtime,
129 error_type=ErrorType.USAGE,
130 log_level=policy.log_level,
131 )
132 raise ExitIntentError(intent)
134 register_signal_handlers()
136 _run_repl_session(quiet=quiet, stdin_isatty=sys.stdin.isatty())
139if __name__ == "__main__":
140 repl_app()