1use 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
12pub struct CliContext {
16 engine: Arc<RwLock<Option<SimulatorEngine>>>,
18 engine_config: EngineConfig,
20 log_config: LogConfig,
22 metrics: Arc<MetricsCollector>,
24 output: OutputWriter,
26 working_dir: PathBuf,
28 verbosity: u8,
30 colors: bool,
32 shutdown: Arc<tokio::sync::Notify>,
34}
35
36impl CliContext {
37 pub fn builder() -> CliContextBuilder {
39 CliContextBuilder::default()
40 }
41
42 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 pub fn engine_ref(&self) -> &Arc<RwLock<Option<SimulatorEngine>>> {
55 &self.engine
56 }
57
58 pub fn engine_config(&self) -> &EngineConfig {
60 &self.engine_config
61 }
62
63 pub fn log_config(&self) -> &LogConfig {
65 &self.log_config
66 }
67
68 pub fn metrics(&self) -> &Arc<MetricsCollector> {
70 &self.metrics
71 }
72
73 pub fn output(&self) -> &OutputWriter {
75 &self.output
76 }
77
78 pub fn output_mut(&mut self) -> &mut OutputWriter {
80 &mut self.output
81 }
82
83 pub fn working_dir(&self) -> &PathBuf {
85 &self.working_dir
86 }
87
88 pub fn verbosity(&self) -> u8 {
90 self.verbosity
91 }
92
93 pub fn is_quiet(&self) -> bool {
95 self.verbosity == 0
96 }
97
98 pub fn is_verbose(&self) -> bool {
100 self.verbosity >= 2
101 }
102
103 pub fn is_debug(&self) -> bool {
105 self.verbosity >= 3
106 }
107
108 pub fn colors_enabled(&self) -> bool {
110 self.colors
111 }
112
113 pub fn shutdown_signal(&self) -> Arc<tokio::sync::Notify> {
115 self.shutdown.clone()
116 }
117
118 pub fn signal_shutdown(&self) {
120 self.shutdown.notify_waiters();
121 }
122
123 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 pub fn println(&self, msg: impl AsRef<str>) {
135 if !self.is_quiet() {
136 println!("{}", msg.as_ref());
137 }
138 }
139
140 pub fn vprintln(&self, msg: impl AsRef<str>) {
142 if self.is_verbose() {
143 println!("{}", msg.as_ref());
144 }
145 }
146
147 pub fn dprintln(&self, msg: impl AsRef<str>) {
149 if self.is_debug() {
150 println!("[DEBUG] {}", msg.as_ref());
151 }
152 }
153}
154
155#[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 pub fn engine_config(mut self, config: EngineConfig) -> Self {
169 self.engine_config = Some(config);
170 self
171 }
172
173 pub fn log_config(mut self, config: LogConfig) -> Self {
175 self.log_config = Some(config);
176 self
177 }
178
179 pub fn output_format(mut self, format: OutputFormat) -> Self {
181 self.output_format = format;
182 self
183 }
184
185 pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
187 self.working_dir = Some(path.into());
188 self
189 }
190
191 pub fn verbosity(mut self, level: u8) -> Self {
193 self.verbosity = level;
194 self
195 }
196
197 pub fn colors(mut self, enabled: bool) -> Self {
199 self.colors = enabled;
200 self
201 }
202
203 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}