use crate::error::{CliError, CliResult};
use crate::output::{OutputFormat, OutputWriter};
use mabi_core::{EngineConfig, LogConfig, MetricsCollector, SimulatorEngine};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct CliContext {
engine: Arc<RwLock<Option<SimulatorEngine>>>,
engine_config: EngineConfig,
log_config: LogConfig,
metrics: Arc<MetricsCollector>,
output: OutputWriter,
working_dir: PathBuf,
verbosity: u8,
colors: bool,
shutdown: Arc<tokio::sync::Notify>,
}
impl CliContext {
pub fn builder() -> CliContextBuilder {
CliContextBuilder::default()
}
pub async fn engine(&self) -> CliResult<Arc<RwLock<Option<SimulatorEngine>>>> {
let mut guard = self.engine.write().await;
if guard.is_none() {
let engine = SimulatorEngine::new(self.engine_config.clone());
*guard = Some(engine);
}
drop(guard);
Ok(self.engine.clone())
}
pub fn engine_ref(&self) -> &Arc<RwLock<Option<SimulatorEngine>>> {
&self.engine
}
pub fn engine_config(&self) -> &EngineConfig {
&self.engine_config
}
pub fn log_config(&self) -> &LogConfig {
&self.log_config
}
pub fn metrics(&self) -> &Arc<MetricsCollector> {
&self.metrics
}
pub fn output(&self) -> &OutputWriter {
&self.output
}
pub fn output_mut(&mut self) -> &mut OutputWriter {
&mut self.output
}
pub fn working_dir(&self) -> &PathBuf {
&self.working_dir
}
pub fn verbosity(&self) -> u8 {
self.verbosity
}
pub fn is_quiet(&self) -> bool {
self.verbosity == 0
}
pub fn is_verbose(&self) -> bool {
self.verbosity >= 2
}
pub fn is_debug(&self) -> bool {
self.verbosity >= 3
}
pub fn colors_enabled(&self) -> bool {
self.colors
}
pub fn shutdown_signal(&self) -> Arc<tokio::sync::Notify> {
self.shutdown.clone()
}
pub fn signal_shutdown(&self) {
self.shutdown.notify_waiters();
}
pub fn resolve_path(&self, path: impl AsRef<std::path::Path>) -> PathBuf {
let path = path.as_ref();
if path.is_absolute() {
path.to_path_buf()
} else {
self.working_dir.join(path)
}
}
pub fn println(&self, msg: impl AsRef<str>) {
if !self.is_quiet() {
println!("{}", msg.as_ref());
}
}
pub fn vprintln(&self, msg: impl AsRef<str>) {
if self.is_verbose() {
println!("{}", msg.as_ref());
}
}
pub fn dprintln(&self, msg: impl AsRef<str>) {
if self.is_debug() {
println!("[DEBUG] {}", msg.as_ref());
}
}
}
#[derive(Default)]
pub struct CliContextBuilder {
engine_config: Option<EngineConfig>,
log_config: Option<LogConfig>,
output_format: OutputFormat,
working_dir: Option<PathBuf>,
verbosity: u8,
colors: bool,
}
impl CliContextBuilder {
pub fn engine_config(mut self, config: EngineConfig) -> Self {
self.engine_config = Some(config);
self
}
pub fn log_config(mut self, config: LogConfig) -> Self {
self.log_config = Some(config);
self
}
pub fn output_format(mut self, format: OutputFormat) -> Self {
self.output_format = format;
self
}
pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.working_dir = Some(path.into());
self
}
pub fn verbosity(mut self, level: u8) -> Self {
self.verbosity = level;
self
}
pub fn colors(mut self, enabled: bool) -> Self {
self.colors = enabled;
self
}
pub fn build(self) -> CliResult<CliContext> {
let working_dir = self
.working_dir
.or_else(|| std::env::current_dir().ok())
.ok_or_else(|| {
CliError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not determine working directory",
))
})?;
Ok(CliContext {
engine: Arc::new(RwLock::new(None)),
engine_config: self.engine_config.unwrap_or_default(),
log_config: self.log_config.unwrap_or_else(|| LogConfig::development()),
metrics: Arc::new(MetricsCollector::new()),
output: OutputWriter::new(self.output_format, self.colors),
working_dir,
verbosity: self.verbosity,
colors: self.colors,
shutdown: Arc::new(tokio::sync::Notify::new()),
})
}
}