Coverage for /home/runner/work/bijux-cli/bijux-cli/src/bijux_cli/commands/config/export.py: 100%
32 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-19 23:36 +0000
« 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
4"""Implements the `config export` subcommand for the Bijux CLI.
6This module contains the logic for exporting the application's entire current
7configuration to a specified destination, which can be a file or standard
8output. The output format can be explicitly set to 'env', 'json', or 'yaml',
9or it can be inferred from the destination file's extension.
11Output Contract:
12 * Success (to file): `{"status": "exported", "file": str, "format": str}`
13 * Success (to stdout): The raw exported configuration data is printed directly.
14 * Verbose (to file): Adds `{"python": str, "platform": str}` to the payload.
15 * Error: `{"error": str, "code": int}`
17Exit Codes:
18 * `0`: Success.
19 * `1` or `2`: An error occurred during the export process, such as a file
20 write error or invalid format request.
21"""
23from __future__ import annotations
25import platform
27import typer
29from bijux_cli.commands.utilities import (
30 ascii_safe,
31 emit_error_and_exit,
32 new_run_command,
33 parse_global_flags,
34)
35from bijux_cli.contracts import ConfigProtocol
36from bijux_cli.core.constants import (
37 HELP_DEBUG,
38 HELP_FORMAT,
39 HELP_NO_PRETTY,
40 HELP_QUIET,
41 HELP_VERBOSE,
42)
43from bijux_cli.core.di import DIContainer
44from bijux_cli.core.exceptions import CommandError
47def export_config(
48 ctx: typer.Context,
49 path: str = typer.Argument(
50 ..., help="Destination file – use “-” to write to STDOUT"
51 ),
52 out_fmt: str = typer.Option(
53 None, "--out-format", help="Force output format: env | json | yaml"
54 ),
55 quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
56 verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
57 fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
58 pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
59 debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
60) -> None:
61 """Exports the current configuration to a file or standard output.
63 This function writes all configuration key-value pairs to a specified
64 destination. If the destination is a file path, a structured JSON/YAML
65 confirmation message is printed to stdout upon success. If the destination
66 is "-", the raw exported configuration is printed directly to stdout.
68 Args:
69 ctx (typer.Context): The Typer context for the CLI.
70 path (str): The destination file path, or "-" for standard output.
71 out_fmt (str): The desired output format ('env', 'json', 'yaml'). If
72 unspecified, it is inferred from the file extension.
73 quiet (bool): If True, suppresses all output except for errors.
74 verbose (bool): If True, includes Python/platform details in the
75 confirmation payload (file export only).
76 fmt (str): The format for the confirmation payload ("json" or "yaml").
77 pretty (bool): If True, pretty-prints the confirmation payload.
78 debug (bool): If True, enables debug diagnostics.
80 Returns:
81 None:
83 Raises:
84 SystemExit: Always exits with a contract-compliant status code and
85 payload, indicating success or detailing the error.
86 """
87 flags = parse_global_flags()
89 quiet = flags["quiet"]
90 verbose = flags["verbose"]
91 fmt = flags["format"]
92 pretty = flags["pretty"]
93 debug = flags["debug"]
95 include_runtime = verbose
96 fmt_lower = fmt.lower()
98 command = "config export"
100 config_svc = DIContainer.current().resolve(ConfigProtocol)
102 try:
103 config_svc.export(path, out_fmt)
104 except CommandError as exc:
105 code = 2 if getattr(exc, "http_status", 0) == 400 else 1
106 emit_error_and_exit(
107 f"Failed to export config: {exc}",
108 code=code,
109 failure="export_failed",
110 command=command,
111 fmt=fmt_lower,
112 quiet=quiet,
113 include_runtime=include_runtime,
114 debug=debug,
115 )
117 if path != "-":
119 def payload_builder(include_runtime: bool) -> dict[str, object]:
120 """Builds the payload confirming a successful export to a file.
122 Args:
123 include_runtime (bool): If True, includes Python and platform info.
125 Returns:
126 dict[str, object]: The structured payload.
127 """
128 payload: dict[str, object] = {
129 "status": "exported",
130 "file": path,
131 "format": out_fmt or "auto",
132 }
133 if include_runtime:
134 payload["python"] = ascii_safe(
135 platform.python_version(), "python_version"
136 )
137 payload["platform"] = ascii_safe(platform.platform(), "platform")
138 return payload
140 new_run_command(
141 command_name=command,
142 payload_builder=payload_builder,
143 quiet=quiet,
144 verbose=verbose,
145 fmt=fmt_lower,
146 pretty=pretty,
147 debug=debug,
148 )