1use crate::cli::init;
4use crate::cli::ConfigAction;
5use crate::config::{
6 expand_path, get_config_path, is_first_run, load_config, save_config, Config, ConfigLock, GeneralConfig,
7};
8use anyhow::Result;
9use std::fs;
10use std::io::{self, Write};
11use std::path::PathBuf;
12use std::process::Command;
13
14pub fn run_config_action(action: ConfigAction) -> Result<()> {
16 match action {
17 ConfigAction::Add {
18 paths,
19 } => add_source_files(&paths),
20 ConfigAction::Edit => edit_config(),
21 ConfigAction::Reset => reset_config(),
22 ConfigAction::Show => show_config(),
23 }
24}
25
26fn add_source_files(raw_paths: &[String]) -> Result<()> {
32 let config_path = get_config_path()?;
33
34 if is_first_run()? {
35 anyhow::bail!("No config found at {}. Run `alf init` to create one.", config_path.display());
36 }
37
38 let _lock = ConfigLock::acquire()?;
39 let mut config = load_config()?;
40 let (to_add, duplicates) = resolve_new_source_files(&config, raw_paths)?;
41
42 for duplicate in &duplicates {
43 println!("Already configured: {}", duplicate);
44 }
45
46 if to_add.is_empty() {
47 println!("No new source files added.");
48 return Ok(());
49 }
50
51 config.general.shell_files.extend(to_add.iter().cloned());
52 save_config(&config)?;
53
54 for added in &to_add {
55 println!("Added: {}", added);
56 }
57
58 println!("Config saved to {}", config_path.display());
59 Ok(())
60}
61
62fn resolve_new_source_files(
77 config: &Config,
78 raw_paths: &[String],
79) -> Result<(Vec<String>, Vec<String>)> {
80 for raw_path in raw_paths {
81 if !expand_path(raw_path).is_absolute() {
82 anyhow::bail!(
83 "Relative paths are not allowed: {}. Use an absolute path or one starting with `~` or `$HOME`.",
84 raw_path
85 );
86 }
87 }
88
89 for raw_path in raw_paths {
90 let expanded = expand_path(raw_path);
91
92 if !expanded.exists() {
93 anyhow::bail!("Shell file not found: {}", expanded.display());
94 }
95
96 if !expanded.is_file() {
97 anyhow::bail!("Shell source path is not a regular file: {}", expanded.display());
98 }
99 }
100
101 let mut configured: Vec<PathBuf> =
102 config.general.shell_files.iter().map(String::as_str).map(canonical_key).collect();
103
104 let mut to_add = Vec::new();
105 let mut duplicates = Vec::new();
106
107 for raw_path in raw_paths {
108 let key = canonical_key(raw_path);
109
110 if configured.contains(&key) {
111 duplicates.push(raw_path.clone());
112 } else {
113 configured.push(key);
114 to_add.push(raw_path.clone());
115 }
116 }
117
118 Ok((to_add, duplicates))
119}
120
121fn canonical_key(raw_path: &str) -> PathBuf {
126 let expanded = expand_path(raw_path);
127 fs::canonicalize(&expanded).unwrap_or(expanded)
128}
129
130fn show_config() -> Result<()> {
132 let config = load_config()?;
133 let config_path = get_config_path()?;
134
135 println!("Location: {}\n", config_path.display());
136 println!("{}", toml::to_string_pretty(&config)?);
137
138 Ok(())
139}
140
141fn edit_config() -> Result<()> {
143 let config_path = get_config_path()?;
144
145 let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
147
148 let status = Command::new(&editor).arg(config_path.to_string_lossy().to_string()).status()?;
149
150 if !status.success() {
151 anyhow::bail!("Editor exited with non-zero status");
152 }
153
154 Ok(())
155}
156
157fn reset_config() -> Result<()> {
159 print!("Are you sure you want to reset configuration? (y/N) ");
160 io::stdout().flush()?;
161
162 let mut response = String::new();
163 io::stdin().read_line(&mut response)?;
164
165 if !response.trim().eq_ignore_ascii_case("y") {
166 println!("Cancelled.");
167 return Ok(());
168 }
169
170 let home = std::env::var("HOME").map_err(|_| anyhow::anyhow!("HOME environment variable is not set"))?;
172 let detected_files = init::detect_shell_files(&home);
173
174 let config = Config {
175 general: GeneralConfig {
176 shell_files: detected_files,
177 ..Default::default()
178 },
179 ..Default::default()
180 };
181
182 save_config(&config)?;
183
184 let config_path = get_config_path()?;
185 println!("Config reset to defaults and saved to {}", config_path.display());
186
187 Ok(())
188}
189
190#[cfg(test)]
191#[path = "config_cmd_tests.rs"]
192mod config_cmd_tests;