Coverage for /home/runner/work/bijux-cli/bijux-cli/src/bijux_cli/cli.py: 100%

17 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-19 23:36 +0000

1# SPDX-License-Identifier: MIT 

2# Copyright © 2025 Bijan Mousavi 

3 

4"""Constructs the main `Typer` application for the Bijux CLI. 

5 

6This module serves as the primary builder for the entire CLI. It defines the 

7root `Typer` app, orchestrates the registration of all core commands and 

8the discovery of dynamic plugins, and sets the default behavior for when the 

9CLI is invoked without any command. 

10""" 

11 

12from __future__ import annotations 

13 

14import subprocess # nosec B404 

15import sys 

16 

17import typer 

18from typer import Context 

19 

20from bijux_cli.commands import register_commands, register_dynamic_plugins 

21 

22 

23def maybe_default_to_repl(ctx: Context) -> None: 

24 """Invokes the `repl` command if no other subcommand is specified. 

25 

26 This function is used as the root callback for the main `Typer` application. 

27 It checks if a subcommand was invoked and, if not, re-executes the CLI 

28 with the `repl` command. 

29 

30 Args: 

31 ctx (Context): The Typer context, used to check for an invoked subcommand. 

32 

33 Returns: 

34 None: 

35 """ 

36 if ctx.invoked_subcommand is None: 

37 subprocess.call([sys.argv[0], "repl"]) # noqa: S603 # nosec B603 

38 

39 

40def build_app() -> typer.Typer: 

41 """Builds and configures the root `Typer` application. 

42 

43 This factory function performs the main steps of assembling the CLI: 

44 1. Creates the root `Typer` app instance. 

45 2. Registers all core, built-in commands. 

46 3. Discovers and registers all dynamic plugins. 

47 4. Sets the default callback to launch the REPL. 

48 

49 Returns: 

50 typer.Typer: The fully constructed `Typer` application. 

51 """ 

52 app = typer.Typer( 

53 help="Bijux CLI – Lean, plug-in‑driven command‑line interface.", 

54 invoke_without_command=True, 

55 context_settings={ 

56 "ignore_unknown_options": True, 

57 "allow_extra_args": True, 

58 }, 

59 ) 

60 register_commands(app) 

61 register_dynamic_plugins(app) 

62 app.callback(invoke_without_command=True)(maybe_default_to_repl) 

63 return app 

64 

65 

66app = build_app() 

67 

68__all__ = ["build_app", "app"]