1use anyhow::Result;
13use std::io::{self, IsTerminal, Write};
14use std::path::{Path, PathBuf};
15
16use crate::{backup, config, db, doctor, importer, shell};
17
18pub fn run(wizard: bool, assume_yes: bool) -> Result<()> {
20 if wizard {
21 run_wizard(assume_yes)
22 } else {
23 run_basic()
24 }
25}
26
27pub fn run_shell_upgrade() -> Result<()> {
33 let Some(bashrc) = bashrc_path() else {
34 eprintln!("Répertoire personnel introuvable : mise à niveau impossible.");
35 std::process::exit(1);
36 };
37
38 match shell::upgrade_block(&bashrc)? {
39 shell::ShellUpgrade::NotInstalled => {
40 println!(
41 "Aucune intégration Bash mnemo détectée dans {}.",
42 display_home(&bashrc)
43 );
44 println!("Lancez `mnemo init` pour l'installer.");
45 }
46 shell::ShellUpgrade::AlreadyCurrent => {
47 println!(
48 "Intégration Bash mnemo déjà à jour dans {} : aucun changement.",
49 display_home(&bashrc)
50 );
51 }
52 shell::ShellUpgrade::Upgraded { backup } => {
53 println!("Intégration Bash mnemo détectée : obsolète.");
54 println!("Sauvegarde créée : {}", display_home(&backup));
55 println!("Bloc d'intégration Bash mnemo mis à niveau.");
56 println!("Prochaine étape : source ~/.bashrc");
57 }
58 }
59 Ok(())
60}
61
62fn run_basic() -> Result<()> {
64 ensure_config_and_db()?;
65
66 println!();
67 println!("Ajoutez ces lignes à votre ~/.bashrc :");
68 println!("------------------------------------------------------------");
69 print!("{}", shell::bashrc_snippet());
70 println!("------------------------------------------------------------");
71 println!("Puis rechargez : source ~/.bashrc");
72 Ok(())
73}
74
75fn run_wizard(assume_yes: bool) -> Result<()> {
77 let interactive = io::stdin().is_terminal();
78 if !interactive && !assume_yes {
79 eprintln!(
80 "mnemo init --wizard nécessite un terminal interactif. En contexte non \
81 interactif (script, CI, pipe), relancez avec --yes pour accepter les \
82 choix sûrs par défaut (jamais de suppression)."
83 );
84 std::process::exit(1);
85 }
86
87 println!("Bienvenue dans mnemo.");
88 println!();
89 println!(
90 "mnemo va vérifier votre installation locale, initialiser la configuration \
91 si nécessaire, puis vous proposer l'intégration Bash et l'import de votre \
92 historique existant. Aucune donnée n'est jamais supprimée."
93 );
94 println!();
95
96 print_install_overview()?;
97 println!();
98
99 ensure_config_and_db()?;
100 println!();
101
102 setup_bash_integration(interactive)?;
103 println!();
104
105 match bash_history_path() {
106 Some(hist) if hist.exists() => {
107 let question = format!("Importer {} maintenant ?", display_home(&hist));
108 if ask(&question, false, interactive)? {
109 import_history(&hist)?;
110 }
111 }
112 Some(hist) => {
113 println!(
114 "Historique {} introuvable : import ignoré.",
115 display_home(&hist)
116 );
117 }
118 None => {}
119 }
120 println!();
121
122 if ask("Lancer mnemo doctor ?", true, interactive)? {
123 println!();
124 if let Err(err) = doctor::run(false, false) {
127 eprintln!("Diagnostic ignoré : {err}");
128 }
129 }
130
131 print_next_steps();
132 Ok(())
133}
134
135fn ensure_config_and_db() -> Result<()> {
138 let cfg_path = config::config_path()?;
139 if cfg_path.exists() {
140 println!("Configuration existante : {}", cfg_path.display());
141 } else {
142 config::Config::default().save(&cfg_path)?;
143 println!("Configuration créée : {}", cfg_path.display());
144 }
145 config::harden_file(&cfg_path);
147
148 let db_path = config::db_path()?;
149 db::open(&db_path)?;
150 config::harden_file(&db_path);
151 println!("Base de données : {}", db_path.display());
152 Ok(())
153}
154
155fn setup_bash_integration(interactive: bool) -> Result<()> {
159 let Some(bashrc) = bashrc_path() else {
160 eprintln!("Répertoire personnel introuvable : intégration Bash ignorée.");
161 return Ok(());
162 };
163 let content = std::fs::read_to_string(&bashrc).unwrap_or_default();
164
165 match shell::block_state(&content) {
166 shell::BlockState::Absent => {
167 if ask(
168 "Ajouter l'intégration Bash dans ~/.bashrc ?",
169 true,
170 interactive,
171 )? {
172 add_bash_integration()?;
173 }
174 }
175 shell::BlockState::Legacy => {
176 if ask(
177 "Mettre à jour l'intégration Bash existante (active les sessions) ?",
178 true,
179 interactive,
180 )? {
181 upgrade_bash_integration(&bashrc)?;
182 }
183 }
184 shell::BlockState::Current => {
185 println!(
186 "Intégration Bash déjà à jour dans {} : aucun changement.",
187 display_home(&bashrc)
188 );
189 }
190 }
191 Ok(())
192}
193
194fn add_bash_integration() -> Result<()> {
197 let Some(bashrc) = bashrc_path() else {
198 eprintln!("Répertoire personnel introuvable : intégration Bash ignorée.");
199 return Ok(());
200 };
201 if shell::install_block(&bashrc)? {
202 println!(
203 "Intégration Bash ajoutée à {} (sauvegarde du fichier créée).",
204 display_home(&bashrc)
205 );
206 } else {
207 println!(
208 "Intégration Bash déjà présente dans {} : aucun changement.",
209 display_home(&bashrc)
210 );
211 }
212 Ok(())
213}
214
215fn upgrade_bash_integration(bashrc: &Path) -> Result<()> {
217 match shell::upgrade_block(bashrc)? {
218 shell::ShellUpgrade::Upgraded { backup } => {
219 println!(
220 "Intégration Bash mise à niveau dans {} (sauvegarde {} créée).",
221 display_home(bashrc),
222 display_home(&backup)
223 );
224 }
225 shell::ShellUpgrade::AlreadyCurrent => {
226 println!("Intégration Bash déjà à jour : aucun changement.");
227 }
228 shell::ShellUpgrade::NotInstalled => {
229 add_bash_integration()?;
231 }
232 }
233 Ok(())
234}
235
236fn import_history(path: &Path) -> Result<()> {
238 let cfg = config::Config::load()?;
239 let conn = db::open(&config::db_path()?)?;
240 let stats = importer::import_bash_history(&conn, path, &cfg)?;
241 println!("Import depuis {}", display_home(path));
242 println!(" Importées : {}", stats.imported);
243 println!(" Sensibles ignorées : {}", stats.skipped_sensitive);
244 println!(" Doublons ignorés : {}", stats.skipped_duplicate);
245 Ok(())
246}
247
248fn print_install_overview() -> Result<()> {
250 println!("Données :");
251 println!(
252 " Configuration : {}",
253 display_home(&config::config_path()?)
254 );
255 println!(" Base SQLite : {}", display_home(&config::db_path()?));
256 if let Ok(exe) = std::env::current_exe() {
257 println!(" Binaire : {}", display_home(&exe));
258 }
259 if let Ok(backups) = backup::backups_dir() {
260 println!(" Sauvegardes : {}", display_home(&backups));
261 }
262 Ok(())
263}
264
265fn print_next_steps() {
267 println!();
268 println!("Prochaines étapes :");
269 println!(" source ~/.bashrc");
270 println!(" mnemo import");
271 println!(" mnemo search");
272 println!(" mnemo doctor");
273}
274
275fn ask(question: &str, default_yes: bool, interactive: bool) -> Result<bool> {
278 if !interactive {
279 return Ok(default_yes);
280 }
281 let suffix = if default_yes { "[O/n]" } else { "[o/N]" };
282 print!("{question} {suffix} ");
283 io::stdout().flush()?;
284
285 let mut answer = String::new();
286 io::stdin().read_line(&mut answer)?;
287 let answer = answer.trim().to_lowercase();
288 if answer.is_empty() {
289 return Ok(default_yes);
290 }
291 Ok(answer == "o" || answer == "oui" || answer == "y" || answer == "yes")
292}
293
294fn bashrc_path() -> Option<PathBuf> {
295 dirs::home_dir().map(|h| h.join(".bashrc"))
296}
297
298fn bash_history_path() -> Option<PathBuf> {
299 dirs::home_dir().map(|h| h.join(".bash_history"))
300}
301
302fn display_home(path: &Path) -> String {
304 if let Some(home) = dirs::home_dir() {
305 if let Ok(rest) = path.strip_prefix(&home) {
306 return format!("~/{}", rest.display());
307 }
308 }
309 path.display().to_string()
310}