Skip to main content

systemprompt_cli/
cli_settings.rs

1//! CLI-wide runtime settings: output format, verbosity, colour, and
2//! interactivity.
3//!
4//! [`CliConfig`] holds the resolved presentation options for a CLI invocation:
5//! defaults, overridden by the [`crate::env_overrides::EnvOverrides`] snapshot
6//! ([`CliConfig::resolve`]), overridden by command-line flags. The
7//! [`OutputFormat`], [`VerbosityLevel`], and [`ColorMode`] enums express the
8//! individual axes. The resolved config travels explicitly on
9//! [`crate::context::CommandContext`]; there is no process-global instance.
10
11use std::io::IsTerminal;
12
13use crate::env_overrides::EnvOverrides;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum OutputFormat {
17    Table,
18    Json,
19    Yaml,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
23pub enum VerbosityLevel {
24    Quiet,
25    Normal,
26    Verbose,
27    Debug,
28}
29
30impl VerbosityLevel {
31    pub const fn as_tracing_filter(&self) -> Option<&'static str> {
32        match self {
33            Self::Quiet => Some("error"),
34            Self::Normal => None,
35            Self::Verbose => Some("debug"),
36            Self::Debug => Some("trace"),
37        }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ColorMode {
43    Auto,
44    Always,
45    Never,
46}
47
48#[derive(Debug, Clone)]
49pub struct CliConfig {
50    pub output_format: OutputFormat,
51    pub verbosity: VerbosityLevel,
52    pub color_mode: ColorMode,
53    pub interactive: bool,
54    pub profile_override: Option<String>,
55}
56
57impl Default for CliConfig {
58    fn default() -> Self {
59        Self {
60            output_format: OutputFormat::Table,
61            verbosity: VerbosityLevel::Normal,
62            color_mode: ColorMode::Auto,
63            interactive: true,
64            profile_override: None,
65        }
66    }
67}
68
69impl CliConfig {
70    #[must_use]
71    pub fn new() -> Self {
72        Self::default()
73    }
74
75    #[must_use]
76    pub fn resolve(env: &EnvOverrides) -> Self {
77        let mut config = Self::default();
78        config.apply_env(env);
79        config
80    }
81
82    pub const fn with_output_format(mut self, format: OutputFormat) -> Self {
83        self.output_format = format;
84        self
85    }
86
87    pub const fn with_verbosity(mut self, level: VerbosityLevel) -> Self {
88        self.verbosity = level;
89        self
90    }
91
92    pub const fn with_color_mode(mut self, mode: ColorMode) -> Self {
93        self.color_mode = mode;
94        self
95    }
96
97    pub const fn with_interactive(mut self, interactive: bool) -> Self {
98        self.interactive = interactive;
99        self
100    }
101
102    pub fn with_profile_override(mut self, profile: Option<String>) -> Self {
103        self.profile_override = profile;
104        self
105    }
106
107    fn apply_env(&mut self, env: &EnvOverrides) {
108        if let Some(format) = &env.output_format {
109            self.output_format = match format.to_lowercase().as_str() {
110                "json" => OutputFormat::Json,
111                "yaml" => OutputFormat::Yaml,
112                "table" => OutputFormat::Table,
113                _ => self.output_format,
114            };
115        }
116
117        if let Some(level) = &env.log_level {
118            self.verbosity = match level.to_lowercase().as_str() {
119                "quiet" => VerbosityLevel::Quiet,
120                "normal" => VerbosityLevel::Normal,
121                "verbose" => VerbosityLevel::Verbose,
122                "debug" => VerbosityLevel::Debug,
123                _ => self.verbosity,
124            };
125        }
126
127        if env.no_color {
128            self.color_mode = ColorMode::Never;
129        }
130
131        if env.non_interactive {
132            self.interactive = false;
133        }
134    }
135
136    pub fn should_use_color(&self) -> bool {
137        match self.color_mode {
138            ColorMode::Always => true,
139            ColorMode::Never => false,
140            ColorMode::Auto => std::io::stdout().is_terminal(),
141        }
142    }
143
144    pub fn is_json_output(&self) -> bool {
145        self.output_format == OutputFormat::Json
146    }
147
148    pub fn should_show_verbose(&self) -> bool {
149        self.verbosity >= VerbosityLevel::Verbose
150    }
151
152    pub fn is_interactive(&self) -> bool {
153        self.interactive && std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
154    }
155
156    pub const fn output_format(&self) -> OutputFormat {
157        self.output_format
158    }
159}