use super::output::print_json;
use crate::client::config::{ConfigPaths, TorcConfig};
use clap::Subcommand;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
#[derive(Subcommand, Debug, Clone)]
#[command(after_long_help = "\
EXAMPLES:
# Show current configuration
torc config show
# Show as JSON
torc config show --format json
# Show config file locations
torc config paths
# Initialize user config
torc config init --user
")]
pub enum ConfigCommands {
#[command(after_long_help = "\
EXAMPLES:
torc config show
torc config show --format json
")]
Show {
#[arg(short, long, default_value = "toml")]
format: String,
},
#[command(after_long_help = "\
EXAMPLES:
torc config paths
")]
Paths,
#[command(after_long_help = "\
EXAMPLES:
torc config init --user
torc config init --local
torc config init --user --force
")]
Init {
#[arg(long)]
system: bool,
#[arg(long)]
user: bool,
#[arg(long)]
local: bool,
#[arg(short, long)]
force: bool,
},
Validate,
}
pub fn handle_config_commands(command: &ConfigCommands) {
match command {
ConfigCommands::Show { format } => show_config(format),
ConfigCommands::Paths => show_paths(),
ConfigCommands::Init {
system,
user,
local,
force,
} => init_config(*system, *user, *local, *force),
ConfigCommands::Validate => validate_config(),
}
}
fn show_config(format: &str) {
let config = match TorcConfig::load() {
Ok(c) => c,
Err(e) => {
eprintln!("Error loading configuration: {}", e);
std::process::exit(1);
}
};
match format {
"toml" => match config.to_toml() {
Ok(toml) => println!("{}", toml),
Err(e) => {
eprintln!("Error serializing config to TOML: {}", e);
std::process::exit(1);
}
},
"json" => print_json(&config, "config"),
_ => {
eprintln!("Unknown format '{}'. Use 'toml' or 'json'.", format);
std::process::exit(1);
}
}
}
fn show_paths() {
let paths = ConfigPaths::new();
println!("Configuration file paths (in priority order):");
println!();
let system_status = if paths.system.exists() {
"exists"
} else {
"not found"
};
println!(" System: {} ({})", paths.system.display(), system_status);
if let Some(user_path) = &paths.user {
let user_status = if user_path.exists() {
"exists"
} else {
"not found"
};
println!(" User: {} ({})", user_path.display(), user_status);
} else {
println!(" User: (not available - could not determine config directory)");
}
let local_status = if paths.local.exists() {
"exists"
} else {
"not found"
};
println!(" Local: {} ({})", paths.local.display(), local_status);
println!();
println!("Environment variables (highest priority):");
println!(" Use double underscore (__) to separate nested keys:");
println!(" TORC_CLIENT__API_URL, TORC_CLIENT__FORMAT, TORC_SERVER__PORT, etc.");
println!();
let existing = paths.existing_paths();
if existing.is_empty() {
println!("No configuration files found. Run 'torc config init --user' to create one.");
} else {
println!(
"Active configuration files: {}",
existing
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ")
);
}
}
fn init_config(system: bool, user: bool, local: bool, force: bool) {
let paths = ConfigPaths::new();
let target_path = if system {
paths.system.clone()
} else if local {
paths.local.clone()
} else if user {
paths.user.clone().unwrap_or_else(|| {
eprintln!("Error: Could not determine user config directory");
std::process::exit(1);
})
} else {
paths.user.clone().unwrap_or_else(|| {
eprintln!("Error: Could not determine user config directory");
eprintln!("Hint: Use --local to create a project-local config instead");
std::process::exit(1);
})
};
write_config_file(&target_path, force);
}
fn write_config_file(path: &PathBuf, force: bool) {
if path.exists() && !force {
eprintln!("Configuration file already exists: {}", path.display());
eprintln!("Use --force to overwrite");
std::process::exit(1);
}
if let Some(parent) = path.parent()
&& !parent.exists()
{
match fs::create_dir_all(parent) {
Ok(_) => println!("Created directory: {}", parent.display()),
Err(e) => {
eprintln!("Error creating directory {}: {}", parent.display(), e);
std::process::exit(1);
}
}
}
let content = TorcConfig::generate_default_config();
match fs::File::create(path) {
Ok(mut file) => match file.write_all(content.as_bytes()) {
Ok(_) => {
println!("Created configuration file: {}", path.display());
println!();
println!("Edit this file to customize your Torc settings.");
println!("Run 'torc config show' to see the effective configuration.");
}
Err(e) => {
eprintln!("Error writing to {}: {}", path.display(), e);
std::process::exit(1);
}
},
Err(e) => {
eprintln!("Error creating {}: {}", path.display(), e);
if path.starts_with("/etc") {
eprintln!("Hint: Creating system config may require root/sudo");
}
std::process::exit(1);
}
}
}
fn validate_config() {
let paths = ConfigPaths::new();
println!("Validating configuration...");
println!();
let existing = paths.existing_paths();
if existing.is_empty() {
println!("No configuration files found (using defaults)");
} else {
println!("Loading configuration from:");
for path in &existing {
println!(" - {}", path.display());
}
}
println!();
let config = match TorcConfig::load() {
Ok(c) => c,
Err(e) => {
eprintln!("Error loading configuration: {}", e);
std::process::exit(1);
}
};
match config.validate() {
Ok(_) => {
println!("Configuration is valid.");
println!();
println!("Key settings:");
println!(" client.api_url = {}", config.client.api_url);
println!(" client.format = {}", config.client.format);
println!(" server.port = {}", config.server.port);
println!(" dash.port = {}", config.dash.port);
}
Err(errors) => {
eprintln!("Configuration has {} error(s):", errors.len());
for error in errors {
eprintln!(" - {}", error);
}
std::process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_paths() {
let paths = ConfigPaths::new();
assert!(paths.system.to_string_lossy().contains("torc"));
}
}