use crate::cli::init;
use crate::cli::ConfigAction;
use crate::config::{
expand_path, get_config_path, is_first_run, load_config, save_config, Config, ConfigLock, GeneralConfig,
};
use anyhow::Result;
use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::Command;
pub fn run_config_action(action: ConfigAction) -> Result<()> {
match action {
ConfigAction::Add {
paths,
} => add_source_files(&paths),
ConfigAction::Edit => edit_config(),
ConfigAction::Reset => reset_config(),
ConfigAction::Show => show_config(),
}
}
fn add_source_files(raw_paths: &[String]) -> Result<()> {
let config_path = get_config_path()?;
if is_first_run()? {
anyhow::bail!("No config found at {}. Run `alf init` to create one.", config_path.display());
}
let _lock = ConfigLock::acquire()?;
let mut config = load_config()?;
let (to_add, duplicates) = resolve_new_source_files(&config, raw_paths)?;
for duplicate in &duplicates {
println!("Already configured: {}", duplicate);
}
if to_add.is_empty() {
println!("No new source files added.");
return Ok(());
}
config.general.shell_files.extend(to_add.iter().cloned());
save_config(&config)?;
for added in &to_add {
println!("Added: {}", added);
}
println!("Config saved to {}", config_path.display());
Ok(())
}
fn resolve_new_source_files(
config: &Config,
raw_paths: &[String],
) -> Result<(Vec<String>, Vec<String>)> {
for raw_path in raw_paths {
if !expand_path(raw_path).is_absolute() {
anyhow::bail!(
"Relative paths are not allowed: {}. Use an absolute path or one starting with `~` or `$HOME`.",
raw_path
);
}
}
for raw_path in raw_paths {
let expanded = expand_path(raw_path);
if !expanded.exists() {
anyhow::bail!("Shell file not found: {}", expanded.display());
}
if !expanded.is_file() {
anyhow::bail!("Shell source path is not a regular file: {}", expanded.display());
}
}
let mut configured: Vec<PathBuf> =
config.general.shell_files.iter().map(String::as_str).map(canonical_key).collect();
let mut to_add = Vec::new();
let mut duplicates = Vec::new();
for raw_path in raw_paths {
let key = canonical_key(raw_path);
if configured.contains(&key) {
duplicates.push(raw_path.clone());
} else {
configured.push(key);
to_add.push(raw_path.clone());
}
}
Ok((to_add, duplicates))
}
fn canonical_key(raw_path: &str) -> PathBuf {
let expanded = expand_path(raw_path);
fs::canonicalize(&expanded).unwrap_or(expanded)
}
fn show_config() -> Result<()> {
let config = load_config()?;
let config_path = get_config_path()?;
println!("Location: {}\n", config_path.display());
println!("{}", toml::to_string_pretty(&config)?);
Ok(())
}
fn edit_config() -> Result<()> {
let config_path = get_config_path()?;
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
let status = Command::new(&editor).arg(config_path.to_string_lossy().to_string()).status()?;
if !status.success() {
anyhow::bail!("Editor exited with non-zero status");
}
Ok(())
}
fn reset_config() -> Result<()> {
print!("Are you sure you want to reset configuration? (y/N) ");
io::stdout().flush()?;
let mut response = String::new();
io::stdin().read_line(&mut response)?;
if !response.trim().eq_ignore_ascii_case("y") {
println!("Cancelled.");
return Ok(());
}
let home = std::env::var("HOME").map_err(|_| anyhow::anyhow!("HOME environment variable is not set"))?;
let detected_files = init::detect_shell_files(&home);
let config = Config {
general: GeneralConfig {
shell_files: detected_files,
..Default::default()
},
..Default::default()
};
save_config(&config)?;
let config_path = get_config_path()?;
println!("Config reset to defaults and saved to {}", config_path.display());
Ok(())
}
#[cfg(test)]
#[path = "config_cmd_tests.rs"]
mod config_cmd_tests;