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

1# SPDX-License-Identifier: Apache-2.0 

2# Copyright © 2025 Bijan Mousavi 

3 

4"""Implements the interactive Read-Eval-Print Loop (REPL) for the Bijux CLI. 

5 

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. 

11 

12The REPL itself operates in a human-readable format. When executing commands, 

13it respects global flags like format or quiet for those specific invocations. 

14 

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""" 

21 

22from __future__ import annotations 

23 

24import sys 

25 

26import typer 

27 

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 

47 

48repl_app = AsyncTyper( 

49 name="repl", 

50 help="Starts an interactive shell with history and tab-completion.", 

51 add_completion=False, 

52) 

53 

54 

55def _run_piped(repl_quiet: bool) -> None: 

56 """Run the REPL in non-interactive mode.""" 

57 _exec_run_piped(repl_quiet) 

58 

59 

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 

63 

64 await _ui_run_interactive() 

65 

66 

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) 

74 

75 

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. 

85 

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. 

89 

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. 

98 

99 Returns: 

100 None: 

101 """ 

102 if ctx.invoked_subcommand: 

103 return 

104 

105 command = "repl" 

106 policy = current_execution_policy() 

107 effective_include_runtime = policy.include_runtime 

108 quiet = policy.quiet 

109 

110 fmt_lower = fmt.strip().lower() 

111 format_value = None 

112 

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) 

133 

134 register_signal_handlers() 

135 

136 _run_repl_session(quiet=quiet, stdin_isatty=sys.stdin.isatty()) 

137 

138 

139if __name__ == "__main__": 

140 repl_app()