Skip to main content

systemprompt_cli/runner/args/
assemble.rs

1//! Assembling the runtime config and the forwarded argument vector.
2//!
3//! Split from the clap definitions next door because these are the only pieces
4//! with behaviour rather than shape: the verbosity/output precedence, and the
5//! reconstruction that decides what a profile-routed subprocess actually
6//! receives.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use super::{Cli, Commands};
12use crate::cli_settings::{CliConfig, ColorMode, OutputFormat, VerbosityLevel};
13use crate::env_overrides::EnvOverrides;
14
15pub fn build_cli_config(cli: &Cli, env: &EnvOverrides) -> CliConfig {
16    let mut cfg = CliConfig::resolve(env);
17
18    if cli.verbosity.debug {
19        cfg = cfg.with_verbosity(VerbosityLevel::Debug);
20    } else if cli.verbosity.verbose {
21        cfg = cfg.with_verbosity(VerbosityLevel::Verbose);
22    } else if cli.verbosity.quiet {
23        cfg = cfg.with_verbosity(VerbosityLevel::Quiet);
24    }
25
26    if cli.output.json {
27        cfg = cfg.with_output_format(OutputFormat::Json);
28    } else if cli.output.yaml {
29        cfg = cfg.with_output_format(OutputFormat::Yaml);
30    }
31
32    if cli.display.no_color {
33        cfg = cfg.with_color_mode(ColorMode::Never);
34    }
35
36    if cli.display.non_interactive {
37        cfg = cfg.with_interactive(false);
38    }
39
40    cfg = cfg.with_profile_override(cli.profile_opts.profile.clone());
41
42    cfg
43}
44
45pub fn reconstruct_args(cli: &Cli) -> Vec<String> {
46    let original: Vec<String> = std::env::args().skip(1).collect();
47    reconstruct_args_from(cli, &original)
48}
49
50// Why: takes the original argv rather than reading `std::env::args()` so the
51// reconstruction can be exercised at all. Reading the process inside the
52// function made every branch below reachable only by however the test binary
53// itself happened to be invoked.
54pub fn reconstruct_args_from(cli: &Cli, original_args: &[String]) -> Vec<String> {
55    let mut args = Vec::new();
56
57    if cli.verbosity.debug {
58        args.push("--debug".to_owned());
59    } else if cli.verbosity.verbose {
60        args.push("--verbose".to_owned());
61    } else if cli.verbosity.quiet {
62        args.push("--quiet".to_owned());
63    }
64
65    if cli.output.json {
66        args.push("--json".to_owned());
67    } else if cli.output.yaml {
68        args.push("--yaml".to_owned());
69    }
70
71    if cli.display.no_color {
72        args.push("--no-color".to_owned());
73    }
74
75    if cli.display.non_interactive {
76        args.push("--non-interactive".to_owned());
77    }
78
79    if let Some(ref profile) = cli.profile_opts.profile {
80        args.push("--profile".to_owned());
81        args.push(profile.clone());
82    }
83
84    let mut skip_next = false;
85    for arg in original_args {
86        if skip_next {
87            skip_next = false;
88            continue;
89        }
90        if arg == "--profile" {
91            skip_next = true;
92            continue;
93        }
94        if arg.starts_with("--profile=") {
95            continue;
96        }
97        if !args.contains(arg)
98            && !matches!(
99                arg.as_str(),
100                "--debug"
101                    | "--verbose"
102                    | "-v"
103                    | "--quiet"
104                    | "-q"
105                    | "--json"
106                    | "--yaml"
107                    | "--no-color"
108                    | "--non-interactive"
109            )
110        {
111            args.push(arg.clone());
112        }
113    }
114
115    args
116}
117
118pub fn has_local_export_flag(command: Option<&Commands>) -> bool {
119    let args: Vec<String> = std::env::args().collect();
120    has_local_export_flag_in(command, &args)
121}
122
123// Why: same split as `reconstruct_args_from`. The `--export` check is one line
124// of logic wrapped around a process read, and the read is what made it
125// untestable.
126pub fn has_local_export_flag_in(command: Option<&Commands>, args: &[String]) -> bool {
127    if !matches!(command, Some(Commands::Analytics(_))) {
128        return false;
129    }
130    args.iter()
131        .any(|arg| arg == "--export" || arg.starts_with("--export="))
132}