use clap::ArgAction;
use clap::Subcommand;
use colored::Colorize;
use r2x_config::Config;
use r2x_logger as logger;
#[derive(Subcommand, Debug, Clone)]
pub enum LogAction {
Show,
Path {
new_path: Option<String>,
},
Set {
#[command(subcommand)]
setting: LogSetAction,
},
}
#[derive(Subcommand, Debug, Clone)]
pub enum LogSetAction {
MaxSize {
bytes: u64,
},
LogPython {
#[arg(action = ArgAction::Set)]
enabled: bool,
},
NoStdout {
#[arg(action = ArgAction::Set)]
enabled: bool,
},
}
pub fn handle_log(action: Option<LogAction>) {
let action = if let Some(action) = action {
action
} else {
println!(
"{}",
"Tip: run `r2x log show` or `r2x log set <key> <value>`.".dimmed()
);
return;
};
match action {
LogAction::Show => show_logging_config(),
LogAction::Path { new_path } => handle_log_path(new_path),
LogAction::Set { setting } => set_logging_config(setting),
}
}
fn show_logging_config() {
match Config::load() {
Ok(config) => {
println!("{}", "Logging Configuration:".bold().green());
println!(
" {}: {}",
"log-python".cyan(),
config.log_python.unwrap_or(false)
);
println!(
" {}: {}",
"no-stdout".cyan(),
config.no_stdout.unwrap_or(false)
);
println!(
" {}: {}",
"max-size".cyan(),
format_max_size(config.log_max_size)
);
println!(" {}: {}", "path".cyan(), resolve_log_path(&config));
}
Err(e) => {
logger::error(&format!("Failed to load config: {}", e));
}
}
}
fn handle_log_path(new_path: Option<String>) {
match Config::load() {
Ok(mut config) => {
if let Some(path) = new_path {
if path.trim().is_empty() {
logger::error("Log path cannot be empty.");
return;
}
config.log_path = Some(path.clone());
if let Err(e) = config.save() {
logger::error(&format!("Failed to save config: {}", e));
return;
}
logger::success(&format!("Set log path to {}", path));
}
println!("{}", resolve_log_path(&config));
}
Err(e) => {
logger::error(&format!("Failed to load config: {}", e));
}
}
}
fn set_logging_config(setting: LogSetAction) {
match Config::load() {
Ok(mut config) => {
let (key, value_display) = match setting {
LogSetAction::MaxSize { bytes } => {
config.log_max_size = Some(bytes);
("max-size", bytes.to_string())
}
LogSetAction::LogPython { enabled } => {
config.log_python = Some(enabled);
("log-python", enabled.to_string())
}
LogSetAction::NoStdout { enabled } => {
config.no_stdout = Some(enabled);
("no-stdout", enabled.to_string())
}
};
if let Err(e) = config.save() {
logger::error(&format!("Failed to save config: {}", e));
return;
}
logger::success(&format!("Set {} = {}", key, value_display));
println!(
"{}",
"Tip: CLI flags still override these settings for a single run.".dimmed()
);
}
Err(e) => {
logger::error(&format!("Failed to load config: {}", e));
}
}
}
fn resolve_log_path(config: &Config) -> String {
config
.log_path
.clone()
.unwrap_or_else(logger::get_log_path_string)
}
fn format_max_size(max_size: Option<u64>) -> String {
match max_size {
Some(bytes) => format!("{} bytes", bytes),
None => "unlimited".to_string(),
}
}
#[cfg(test)]
mod tests {
use crate::commands::log::{handle_log, LogAction, LogSetAction};
use r2x_config::Config;
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn with_temp_config(f: impl FnOnce()) {
let _guard = ENV_LOCK.lock();
let Ok(dir) = tempfile::tempdir() else {
return;
};
let config_path = dir.path().join("config.toml");
std::env::set_var("R2X_CONFIG", &config_path);
f();
std::env::remove_var("R2X_CONFIG");
}
#[test]
fn test_log_set_no_stdout() {
with_temp_config(|| {
handle_log(Some(LogAction::Set {
setting: LogSetAction::NoStdout { enabled: true },
}));
let Ok(config) = Config::load() else {
return;
};
assert_eq!(config.no_stdout, Some(true));
});
}
#[test]
fn test_log_set_size() {
with_temp_config(|| {
handle_log(Some(LogAction::Set {
setting: LogSetAction::MaxSize {
bytes: 10 * 1024 * 1024,
},
}));
let Ok(config) = Config::load() else {
return;
};
assert_eq!(config.log_max_size, Some(10 * 1024 * 1024));
});
}
}