fg/
lib.rs

1use std::error::Error;
2use std::process::Command;
3use std::fs;
4use std::path::PathBuf;
5use std::collections::HashMap;
6
7fn get_config_dir() -> PathBuf {
8    let mut path = dirs::config_dir().unwrap_or_else(|| {
9        let mut fallback = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
10        fallback.push(".config");
11        fallback
12    });
13    path.push("fg");
14    path
15}
16
17fn get_config_file() -> PathBuf {
18    let mut path = get_config_dir();
19    path.push("config.json");
20    path
21}
22
23fn get_aliases_file() -> PathBuf {
24    let mut path = get_config_dir();
25    path.push("aliases.json");
26    path
27}
28
29#[derive(serde::Serialize, serde::Deserialize, Default)]
30struct Config {
31    mode: String, // "git" or "gh"
32}
33
34#[derive(serde::Serialize, serde::Deserialize, Default)]
35struct Aliases {
36    commands: HashMap<String, Vec<String>>,
37}
38
39fn load_config() -> Config {
40    let config_file = get_config_file();
41    if let Ok(content) = fs::read_to_string(config_file) {
42        serde_json::from_str(&content).unwrap_or_default()
43    } else {
44        Config { mode: "git".to_string() }
45    }
46}
47
48fn save_config(config: &Config) -> Result<(), Box<dyn Error>> {
49    let config_dir = get_config_dir();
50    fs::create_dir_all(&config_dir)?;
51    let config_file = get_config_file();
52    let content = serde_json::to_string_pretty(config)?;
53    fs::write(config_file, content)?;
54    Ok(())
55}
56
57fn load_aliases() -> Aliases {
58    let aliases_file = get_aliases_file();
59    if let Ok(content) = fs::read_to_string(aliases_file) {
60        serde_json::from_str(&content).unwrap_or_default()
61    } else {
62        Aliases::default()
63    }
64}
65
66fn save_aliases(aliases: &Aliases) -> Result<(), Box<dyn Error>> {
67    let config_dir = get_config_dir();
68    fs::create_dir_all(&config_dir)?;
69    let aliases_file = get_aliases_file();
70    let content = serde_json::to_string_pretty(aliases)?;
71    fs::write(aliases_file, content)?;
72    Ok(())
73}
74
75pub fn set_mode(mode: &str) -> Result<(), Box<dyn Error>> {
76    if mode != "git" && mode != "gh" {
77        return Err("Mode must be 'git' or 'gh'".into());
78    }
79    let config = Config { mode: mode.to_string() };
80    save_config(&config)?;
81    println!("Mode set to: {}", mode);
82    Ok(())
83}
84
85pub fn create_alias(name: &str, commands: Vec<String>) -> Result<(), Box<dyn Error>> {
86    let mut aliases = load_aliases();
87    aliases.commands.insert(name.to_string(), commands.clone());
88    save_aliases(&aliases)?;
89    println!("Alias '{}' created with commands: {:?}", name, commands);
90    Ok(())
91}
92
93pub fn run_alias(name: &str) -> Result<(), Box<dyn Error>> {
94    let aliases = load_aliases();
95    if let Some(commands) = aliases.commands.get(name) {
96        let config = load_config();
97        for cmd in commands {
98            run_command(&config.mode, cmd)?;
99        }
100        Ok(())
101    } else {
102        Err(format!("Alias '{}' not found", name).into())
103    }
104}
105
106pub fn list_aliases() -> Result<(), Box<dyn Error>> {
107    let aliases = load_aliases();
108    if aliases.commands.is_empty() {
109        println!("No aliases configured");
110    } else {
111        println!("Configured aliases:");
112        for (name, commands) in &aliases.commands {
113            println!("  {} -> {:?}", name, commands);
114        }
115    }
116    Ok(())
117}
118
119pub fn get_current_mode() -> Result<(), Box<dyn Error>> {
120    let config = load_config();
121    println!("Current mode: {}", config.mode);
122    Ok(())
123}
124
125fn run_command(mode: &str, cmd: &str) -> Result<(), Box<dyn Error>> {
126    let status = Command::new(mode).args(cmd.split_whitespace()).status()?;
127    if status.success() {
128        println!("{} {} -> OK", mode, cmd);
129        Ok(())
130    } else {
131        Err(format!("{} {} failed (status: {})", mode, cmd, status).into())
132    }
133}
134
135pub fn git_init() -> Result<(), Box<dyn Error>> {
136    let config = load_config();
137    let status = Command::new(&config.mode).arg("init").status()?;
138    if status.success() {
139        println!("{} init -> OK", config.mode);
140        Ok(())
141    } else {
142        Err(format!("{} init failed (status: {})", config.mode, status).into())
143    }
144}
145
146pub fn git_add(archive: &str) -> Result<(), Box<dyn Error>> {
147    let config = load_config();
148    let path = if archive.is_empty() { "." } else { archive };
149    let status = Command::new(&config.mode).arg("add").arg(path).status()?;
150    if status.success() {
151        println!("{} add {} -> OK", config.mode, path);
152        Ok(())
153    } else {
154        Err(format!("{} add failed (status: {})", config.mode, status).into())
155    }
156}
157
158pub fn git_commit(text: &str) -> Result<(), Box<dyn Error>> {
159    let config = load_config();
160    let status = Command::new(&config.mode).arg("commit").arg("-m").arg(text).status()?;
161    if status.success() {
162        println!("{} commit -> OK ({})", config.mode, text);
163        Ok(())
164    } else {
165        Err(format!("{} commit failed (status: {})", config.mode, status).into())
166    }
167}
168
169pub fn git_pull(remote: &str) -> Result<(), Box<dyn Error>> {
170    let config = load_config();
171    let remote_arg = if remote.is_empty() { "origin" } else { remote };
172    let status = Command::new(&config.mode).arg("pull").arg(remote_arg).status()?;
173    if status.success() {
174        println!("{} pull {} -> OK", config.mode, remote_arg);
175        Ok(())
176    } else {
177        Err(format!("{} pull failed (status: {})", config.mode, status).into())
178    }
179}
180
181pub fn git_push(remote: &str) -> Result<(), Box<dyn Error>> {
182    let config = load_config();
183    let remote_arg = if remote.is_empty() { "origin" } else { remote };
184    let status = Command::new(&config.mode).arg("push").arg(remote_arg).status()?;
185    if status.success() {
186        println!("{} push {} -> OK", config.mode, remote_arg);
187        Ok(())
188    } else {
189        Err(format!("{} push failed (status: {})", config.mode, status).into())
190    }
191}
192
193pub fn git_set_branch(branch: &str) -> Result<(), Box<dyn Error>> {
194    let config = load_config();
195    let status = Command::new(&config.mode).arg("checkout").arg("-b").arg(branch).status()?;
196    if status.success() {
197        println!("{} setBranch {} -> OK", config.mode, branch);
198        Ok(())
199    } else {
200        Err(format!("{} setBranch failed (status: {})", config.mode, status).into())
201    }
202}
203
204pub fn git_ro(repository: &str) -> Result<(), Box<dyn Error>> {
205    let config = load_config();
206    let status = Command::new(&config.mode)
207        .arg("remote")
208        .arg("add")
209        .arg("origin")
210        .arg(repository)
211        .status()?;
212    if status.success() {
213        println!("{} remote add origin {} -> OK", config.mode, repository);
214        Ok(())
215    } else {
216        Err(format!("{} remote add failed (status: {})", config.mode, status).into())
217    }
218}
219
220pub fn git_info(target: &str) -> Result<(), Box<dyn Error>> {
221    let config = load_config();
222    let arg = if target.is_empty() { "." } else { target };
223    let status = Command::new(&config.mode).arg("status").arg(arg).status()?;
224    if status.success() {
225        println!("{} status {} -> OK", config.mode, arg);
226        Ok(())
227    } else {
228        Err(format!("{} status failed (status: {})", config.mode, status).into())
229    }
230}
231
232pub fn git_new(value: &str) -> Result<(), Box<dyn Error>> {
233    let config = load_config();
234    let status = Command::new(&config.mode).arg("checkout").arg("-b").arg(value).status()?;
235    if status.success() {
236        println!("{} checkout -b {} -> OK", config.mode, value);
237        Ok(())
238    } else {
239        Err(format!("{} checkout failed (status: {})", config.mode, status).into())
240    }
241}
242
243pub fn get_flag_value(args: &[String], flag: &str) -> Option<String> {
244    for (i, a) in args.iter().enumerate() {
245        if let Some(v) = a.strip_prefix(&format!("{}=", flag)) {
246            return Some(v.to_string());
247        }
248        if a == flag {
249            if let Some(next) = args.get(i + 1) {
250                if !next.starts_with('-') {
251                    return Some(next.clone());
252                } else {
253                    return Some(String::new());
254                }
255            } else {
256                return Some(String::new());
257            }
258        }
259    }
260    None
261}