Skip to main content

mabi_cli/
context.rs

1//! CLI execution context.
2//!
3//! Provides shared state and configuration for command execution.
4
5use crate::error::{CliError, CliResult};
6use crate::output::{OutputFormat, OutputWriter};
7use mabi_core::{EngineConfig, LogConfig, MetricsCollector, SimulatorEngine};
8use std::path::PathBuf;
9use std::sync::Arc;
10use tokio::sync::RwLock;
11
12/// CLI execution context.
13///
14/// Contains shared state and configuration for all commands.
15pub struct CliContext {
16    /// Simulator engine instance.
17    engine: Arc<RwLock<Option<SimulatorEngine>>>,
18    /// Engine configuration.
19    engine_config: EngineConfig,
20    /// Log configuration.
21    log_config: LogConfig,
22    /// Metrics collector.
23    metrics: Arc<MetricsCollector>,
24    /// Output writer.
25    output: OutputWriter,
26    /// Working directory.
27    working_dir: PathBuf,
28    /// Verbosity level (0 = quiet, 1 = normal, 2 = verbose, 3 = debug).
29    verbosity: u8,
30    /// Whether to use colors in output.
31    colors: bool,
32    /// Global shutdown signal.
33    shutdown: Arc<tokio::sync::Notify>,
34}
35
36impl CliContext {
37    /// Create a new CLI context builder.
38    pub fn builder() -> CliContextBuilder {
39        CliContextBuilder::default()
40    }
41
42    /// Get the simulator engine, creating it if necessary.
43    pub async fn engine(&self) -> CliResult<Arc<RwLock<Option<SimulatorEngine>>>> {
44        let mut guard = self.engine.write().await;
45        if guard.is_none() {
46            let engine = SimulatorEngine::new(self.engine_config.clone());
47            *guard = Some(engine);
48        }
49        drop(guard);
50        Ok(self.engine.clone())
51    }
52
53    /// Get the raw engine reference.
54    pub fn engine_ref(&self) -> &Arc<RwLock<Option<SimulatorEngine>>> {
55        &self.engine
56    }
57
58    /// Get the engine configuration.
59    pub fn engine_config(&self) -> &EngineConfig {
60        &self.engine_config
61    }
62
63    /// Get the log configuration.
64    pub fn log_config(&self) -> &LogConfig {
65        &self.log_config
66    }
67
68    /// Get the metrics collector.
69    pub fn metrics(&self) -> &Arc<MetricsCollector> {
70        &self.metrics
71    }
72
73    /// Get the output writer.
74    pub fn output(&self) -> &OutputWriter {
75        &self.output
76    }
77
78    /// Get the output writer mutably.
79    pub fn output_mut(&mut self) -> &mut OutputWriter {
80        &mut self.output
81    }
82
83    /// Get the working directory.
84    pub fn working_dir(&self) -> &PathBuf {
85        &self.working_dir
86    }
87
88    /// Get the verbosity level.
89    pub fn verbosity(&self) -> u8 {
90        self.verbosity
91    }
92
93    /// Check if quiet mode is enabled.
94    pub fn is_quiet(&self) -> bool {
95        self.verbosity == 0
96    }
97
98    /// Check if verbose mode is enabled.
99    pub fn is_verbose(&self) -> bool {
100        self.verbosity >= 2
101    }
102
103    /// Check if debug mode is enabled.
104    pub fn is_debug(&self) -> bool {
105        self.verbosity >= 3
106    }
107
108    /// Check if colors are enabled.
109    pub fn colors_enabled(&self) -> bool {
110        self.colors
111    }
112
113    /// Get the shutdown signal.
114    pub fn shutdown_signal(&self) -> Arc<tokio::sync::Notify> {
115        self.shutdown.clone()
116    }
117
118    /// Signal shutdown.
119    pub fn signal_shutdown(&self) {
120        self.shutdown.notify_waiters();
121    }
122
123    /// Resolve a path relative to the working directory.
124    pub fn resolve_path(&self, path: impl AsRef<std::path::Path>) -> PathBuf {
125        let path = path.as_ref();
126        if path.is_absolute() {
127            path.to_path_buf()
128        } else {
129            self.working_dir.join(path)
130        }
131    }
132
133    /// Print a message if not in quiet mode.
134    pub fn println(&self, msg: impl AsRef<str>) {
135        if !self.is_quiet() {
136            println!("{}", msg.as_ref());
137        }
138    }
139
140    /// Print a verbose message.
141    pub fn vprintln(&self, msg: impl AsRef<str>) {
142        if self.is_verbose() {
143            println!("{}", msg.as_ref());
144        }
145    }
146
147    /// Print a debug message.
148    pub fn dprintln(&self, msg: impl AsRef<str>) {
149        if self.is_debug() {
150            println!("[DEBUG] {}", msg.as_ref());
151        }
152    }
153}
154
155/// Builder for CLI context.
156#[derive(Default)]
157pub struct CliContextBuilder {
158    engine_config: Option<EngineConfig>,
159    log_config: Option<LogConfig>,
160    output_format: OutputFormat,
161    working_dir: Option<PathBuf>,
162    verbosity: u8,
163    colors: bool,
164}
165
166impl CliContextBuilder {
167    /// Set the engine configuration.
168    pub fn engine_config(mut self, config: EngineConfig) -> Self {
169        self.engine_config = Some(config);
170        self
171    }
172
173    /// Set the log configuration.
174    pub fn log_config(mut self, config: LogConfig) -> Self {
175        self.log_config = Some(config);
176        self
177    }
178
179    /// Set the output format.
180    pub fn output_format(mut self, format: OutputFormat) -> Self {
181        self.output_format = format;
182        self
183    }
184
185    /// Set the working directory.
186    pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
187        self.working_dir = Some(path.into());
188        self
189    }
190
191    /// Set the verbosity level.
192    pub fn verbosity(mut self, level: u8) -> Self {
193        self.verbosity = level;
194        self
195    }
196
197    /// Enable or disable colors.
198    pub fn colors(mut self, enabled: bool) -> Self {
199        self.colors = enabled;
200        self
201    }
202
203    /// Build the CLI context.
204    pub fn build(self) -> CliResult<CliContext> {
205        let working_dir = self
206            .working_dir
207            .or_else(|| std::env::current_dir().ok())
208            .ok_or_else(|| CliError::Io(std::io::Error::new(
209                std::io::ErrorKind::NotFound,
210                "Could not determine working directory",
211            )))?;
212
213        Ok(CliContext {
214            engine: Arc::new(RwLock::new(None)),
215            engine_config: self.engine_config.unwrap_or_default(),
216            log_config: self.log_config.unwrap_or_else(|| LogConfig::development()),
217            metrics: Arc::new(MetricsCollector::new()),
218            output: OutputWriter::new(self.output_format, self.colors),
219            working_dir,
220            verbosity: self.verbosity,
221            colors: self.colors,
222            shutdown: Arc::new(tokio::sync::Notify::new()),
223        })
224    }
225}