use anyhow::Result;
use std::io::{self, IsTerminal, Write};
use std::path::{Path, PathBuf};
use crate::{backup, config, db, doctor, importer, shell};
pub fn run(wizard: bool, assume_yes: bool) -> Result<()> {
if wizard {
run_wizard(assume_yes)
} else {
run_basic()
}
}
pub fn run_shell_upgrade() -> Result<()> {
let Some(bashrc) = bashrc_path() else {
eprintln!("Répertoire personnel introuvable : mise à niveau impossible.");
std::process::exit(1);
};
match shell::upgrade_block(&bashrc)? {
shell::ShellUpgrade::NotInstalled => {
println!(
"Aucune intégration Bash mnemo détectée dans {}.",
display_home(&bashrc)
);
println!("Lancez `mnemo init` pour l'installer.");
}
shell::ShellUpgrade::AlreadyCurrent => {
println!(
"Intégration Bash mnemo déjà à jour dans {} : aucun changement.",
display_home(&bashrc)
);
}
shell::ShellUpgrade::Upgraded { backup } => {
println!("Intégration Bash mnemo détectée : obsolète.");
println!("Sauvegarde créée : {}", display_home(&backup));
println!("Bloc d'intégration Bash mnemo mis à niveau.");
println!("Prochaine étape : source ~/.bashrc");
}
}
Ok(())
}
fn run_basic() -> Result<()> {
ensure_config_and_db()?;
println!();
println!("Ajoutez ces lignes à votre ~/.bashrc :");
println!("------------------------------------------------------------");
print!("{}", shell::bashrc_snippet());
println!("------------------------------------------------------------");
println!("Puis rechargez : source ~/.bashrc");
Ok(())
}
fn run_wizard(assume_yes: bool) -> Result<()> {
let interactive = io::stdin().is_terminal();
if !interactive && !assume_yes {
eprintln!(
"mnemo init --wizard nécessite un terminal interactif. En contexte non \
interactif (script, CI, pipe), relancez avec --yes pour accepter les \
choix sûrs par défaut (jamais de suppression)."
);
std::process::exit(1);
}
println!("Bienvenue dans mnemo.");
println!();
println!(
"mnemo va vérifier votre installation locale, initialiser la configuration \
si nécessaire, puis vous proposer l'intégration Bash et l'import de votre \
historique existant. Aucune donnée n'est jamais supprimée."
);
println!();
print_install_overview()?;
println!();
ensure_config_and_db()?;
println!();
setup_bash_integration(interactive)?;
println!();
match bash_history_path() {
Some(hist) if hist.exists() => {
let question = format!("Importer {} maintenant ?", display_home(&hist));
if ask(&question, false, interactive)? {
import_history(&hist)?;
}
}
Some(hist) => {
println!(
"Historique {} introuvable : import ignoré.",
display_home(&hist)
);
}
None => {}
}
println!();
if ask("Lancer mnemo doctor ?", true, interactive)? {
println!();
if let Err(err) = doctor::run(false, false) {
eprintln!("Diagnostic ignoré : {err}");
}
}
print_next_steps();
Ok(())
}
fn ensure_config_and_db() -> Result<()> {
let cfg_path = config::config_path()?;
if cfg_path.exists() {
println!("Configuration existante : {}", cfg_path.display());
} else {
config::Config::default().save(&cfg_path)?;
println!("Configuration créée : {}", cfg_path.display());
}
config::harden_file(&cfg_path);
let db_path = config::db_path()?;
db::open(&db_path)?;
config::harden_file(&db_path);
println!("Base de données : {}", db_path.display());
Ok(())
}
fn setup_bash_integration(interactive: bool) -> Result<()> {
let Some(bashrc) = bashrc_path() else {
eprintln!("Répertoire personnel introuvable : intégration Bash ignorée.");
return Ok(());
};
let content = std::fs::read_to_string(&bashrc).unwrap_or_default();
match shell::block_state(&content) {
shell::BlockState::Absent => {
if ask(
"Ajouter l'intégration Bash dans ~/.bashrc ?",
true,
interactive,
)? {
add_bash_integration()?;
}
}
shell::BlockState::Legacy => {
if ask(
"Mettre à jour l'intégration Bash existante (active les sessions) ?",
true,
interactive,
)? {
upgrade_bash_integration(&bashrc)?;
}
}
shell::BlockState::Current => {
println!(
"Intégration Bash déjà à jour dans {} : aucun changement.",
display_home(&bashrc)
);
}
}
Ok(())
}
fn add_bash_integration() -> Result<()> {
let Some(bashrc) = bashrc_path() else {
eprintln!("Répertoire personnel introuvable : intégration Bash ignorée.");
return Ok(());
};
if shell::install_block(&bashrc)? {
println!(
"Intégration Bash ajoutée à {} (sauvegarde du fichier créée).",
display_home(&bashrc)
);
} else {
println!(
"Intégration Bash déjà présente dans {} : aucun changement.",
display_home(&bashrc)
);
}
Ok(())
}
fn upgrade_bash_integration(bashrc: &Path) -> Result<()> {
match shell::upgrade_block(bashrc)? {
shell::ShellUpgrade::Upgraded { backup } => {
println!(
"Intégration Bash mise à niveau dans {} (sauvegarde {} créée).",
display_home(bashrc),
display_home(&backup)
);
}
shell::ShellUpgrade::AlreadyCurrent => {
println!("Intégration Bash déjà à jour : aucun changement.");
}
shell::ShellUpgrade::NotInstalled => {
add_bash_integration()?;
}
}
Ok(())
}
fn import_history(path: &Path) -> Result<()> {
let cfg = config::Config::load()?;
let conn = db::open(&config::db_path()?)?;
let stats = importer::import_bash_history(&conn, path, &cfg)?;
println!("Import depuis {}", display_home(path));
println!(" Importées : {}", stats.imported);
println!(" Sensibles ignorées : {}", stats.skipped_sensitive);
println!(" Doublons ignorés : {}", stats.skipped_duplicate);
Ok(())
}
fn print_install_overview() -> Result<()> {
println!("Données :");
println!(
" Configuration : {}",
display_home(&config::config_path()?)
);
println!(" Base SQLite : {}", display_home(&config::db_path()?));
if let Ok(exe) = std::env::current_exe() {
println!(" Binaire : {}", display_home(&exe));
}
if let Ok(backups) = backup::backups_dir() {
println!(" Sauvegardes : {}", display_home(&backups));
}
Ok(())
}
fn print_next_steps() {
println!();
println!("Prochaines étapes :");
println!(" source ~/.bashrc");
println!(" mnemo import");
println!(" mnemo search");
println!(" mnemo doctor");
}
fn ask(question: &str, default_yes: bool, interactive: bool) -> Result<bool> {
if !interactive {
return Ok(default_yes);
}
let suffix = if default_yes { "[O/n]" } else { "[o/N]" };
print!("{question} {suffix} ");
io::stdout().flush()?;
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
let answer = answer.trim().to_lowercase();
if answer.is_empty() {
return Ok(default_yes);
}
Ok(answer == "o" || answer == "oui" || answer == "y" || answer == "yes")
}
fn bashrc_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".bashrc"))
}
fn bash_history_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".bash_history"))
}
fn display_home(path: &Path) -> String {
if let Some(home) = dirs::home_dir() {
if let Ok(rest) = path.strip_prefix(&home) {
return format!("~/{}", rest.display());
}
}
path.display().to_string()
}