use anyhow::{Context, Result};
use std::fs;
use std::io::{self, BufRead, Write};
use std::path::{Path, PathBuf};
use crate::config;
use crate::xdg;
const SHELL_FUNCTION: &str = include_str!("../scripts/shell.sh");
const SOURCE_LINE: &str = r#"source "$HOME/.config/gimme/shell.sh""#;
enum Shell {
Bash,
Zsh,
}
pub fn run() {
let gimme_dir = match xdg::gimme_dir() {
Some(d) => d,
None => {
eprintln!("ERROR Could not determine home directory.");
return;
}
};
let shell = detect_shell();
install_shell_function(&gimme_dir, shell.as_ref());
let config_exists = config::find_config_file().is_some();
if config_exists {
let path = config::find_config_file().unwrap();
eprintln!(
"Config already exists at {}, skipping wizard.",
path.display()
);
} else {
run_wizard(&gimme_dir);
}
if let Some(ref sh) = shell {
eprintln!();
let rc = rc_file_for(sh);
eprintln!("Restart your shell or run: source {}", rc.display());
}
if !config_exists {
print_getting_started();
}
}
fn detect_shell() -> Option<Shell> {
let shell_env = std::env::var("SHELL").unwrap_or_default();
if shell_env.ends_with("bash") {
return Some(Shell::Bash);
}
if shell_env.ends_with("zsh") {
return Some(Shell::Zsh);
}
eprintln!("WARN Could not detect a supported shell (bash/zsh) from $SHELL=\"{shell_env}\".");
eprintln!(" You can manually source the shell function from: <gimme_dir>/shell.sh");
None
}
fn rc_file_for(shell: &Shell) -> PathBuf {
let home = dirs::home_dir().unwrap_or_default();
match shell {
Shell::Bash => home.join(".bashrc"),
Shell::Zsh => home.join(".zshrc"),
}
}
fn shell_name(shell: &Shell) -> &'static str {
match shell {
Shell::Bash => "bash",
Shell::Zsh => "zsh",
}
}
fn install_shell_function(gimme_dir: &Path, shell: Option<&Shell>) {
if let Err(e) = write_shell_function(gimme_dir) {
eprintln!("ERROR Failed to write shell function: {e}");
return;
}
let Some(shell) = shell else { return };
eprintln!("Detected shell: {}", shell_name(shell));
let rc = rc_file_for(shell);
match shim_rc_file(&rc) {
Ok(true) => eprintln!("Added source line to {}", rc.display()),
Ok(false) => eprintln!("{} already configured, no changes needed.", rc.display()),
Err(e) => eprintln!("ERROR Failed to update {}: {e}", rc.display()),
}
}
fn write_shell_function(gimme_dir: &Path) -> Result<()> {
fs::create_dir_all(gimme_dir).with_context(|| format!("creating {}", gimme_dir.display()))?;
let dest = gimme_dir.join("shell.sh");
let verb = if dest.exists() { "Updated" } else { "Wrote" };
fs::write(&dest, SHELL_FUNCTION).with_context(|| format!("writing {}", dest.display()))?;
eprintln!("{verb} shell function at {}", dest.display());
Ok(())
}
fn shim_rc_file(rc_path: &Path) -> Result<bool> {
let contents = fs::read_to_string(rc_path).unwrap_or_default();
if contents.contains(SOURCE_LINE) {
return Ok(false);
}
let mut file = fs::OpenOptions::new()
.create(true)
.append(true)
.open(rc_path)
.with_context(|| format!("opening {}", rc_path.display()))?;
writeln!(file)?;
writeln!(file, "# gimme shell integration")?;
writeln!(file, "{SOURCE_LINE}")?;
Ok(true)
}
fn run_wizard(gimme_dir: &Path) {
eprintln!();
let mut folders = Vec::new();
let first = prompt("Where do you keep your repositories?", "~/code");
folders.push(first);
loop {
let extra = prompt("Add another search folder? (path or Enter to skip)", "");
if extra.is_empty() {
break;
}
folders.push(extra);
}
let config_path = gimme_dir.join("config.yaml");
match write_initial_config(&config_path, &folders) {
Ok(()) => {
eprintln!();
eprintln!("Wrote config to {}", config_path.display());
eprintln!(" Search folders: {}", folders.join(", "));
eprintln!(" Protected branches: main, master");
}
Err(e) => {
eprintln!("ERROR Failed to write config: {e}");
}
}
}
fn write_initial_config(path: &Path, search_folders: &[String]) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let mut config = config::Config::default();
config.search_folders = search_folders.to_vec();
let yaml = serde_yaml::to_string(&config)?;
fs::write(path, yaml)?;
Ok(())
}
fn prompt(question: &str, default: &str) -> String {
if default.is_empty() {
eprint!("{question} ");
} else {
eprint!("{question} [{default}]: ");
}
io::stderr().flush().ok();
let stdin = io::stdin();
let mut line = String::new();
stdin.lock().read_line(&mut line).ok();
let trimmed = line.trim();
if trimmed.is_empty() {
default.to_string()
} else {
trimmed.to_string()
}
}
fn print_getting_started() {
eprintln!();
eprintln!("Setup complete! Here are some things you can do:");
eprintln!();
eprintln!(" gimme <repo> Jump to a repository");
eprintln!(" gimme list See all your repositories");
eprintln!(" gimme config add alias k kern Create shortcuts for repos");
eprintln!(" gimme pin Pin the current repo for priority searching");
eprintln!(" gimme clean -b Clean up merged branches");
eprintln!();
eprintln!("Run gimme --help for the full list.");
}