@@ -7,29 +7,54 @@ use crate::db::Database; |
| 7 | 7 | |
| 8 | 8 | use super::Result; |
| 9 | 9 | |
| 10 | +/// Maximum score for imported entries (prevents one source from dominating). |
| 11 | +const MAX_IMPORT_SCORE: f64 = 100.0; |
| 12 | + |
| 10 | 13 | /// Import directories from various tools (zoxide, autojump, z, fasd). |
| 11 | 14 | pub fn run() -> Result<()> { |
| 12 | 15 | let mut db = Database::open()?; |
| 13 | 16 | let mut total_imported = 0; |
| 14 | 17 | |
| 15 | | - // Try each source |
| 16 | | - total_imported += import_zoxide(&mut db)?; |
| 17 | | - total_imported += import_autojump(&mut db)?; |
| 18 | | - total_imported += import_z(&mut db)?; |
| 19 | | - total_imported += import_fasd(&mut db)?; |
| 18 | + // Collect entries from each source with their raw scores |
| 19 | + let mut entries: Vec<(String, f64)> = Vec::new(); |
| 20 | + |
| 21 | + collect_zoxide(&mut entries)?; |
| 22 | + collect_autojump(&mut entries)?; |
| 23 | + collect_z(&mut entries)?; |
| 24 | + collect_fasd(&mut entries)?; |
| 25 | + |
| 26 | + if entries.is_empty() { |
| 27 | + println!("No databases found to import from"); |
| 28 | + return Ok(()); |
| 29 | + } |
| 30 | + |
| 31 | + // Find max score for normalization |
| 32 | + let max_score = entries.iter().map(|(_, s)| *s).fold(0.0f64, f64::max); |
| 33 | + |
| 34 | + // Normalize and import |
| 35 | + for (path, score) in entries { |
| 36 | + // Scale score to 0-MAX_IMPORT_SCORE range |
| 37 | + let normalized = if max_score > 0.0 { |
| 38 | + (score / max_score) * MAX_IMPORT_SCORE |
| 39 | + } else { |
| 40 | + 1.0 |
| 41 | + }; |
| 42 | + |
| 43 | + if db.import_entry(&path, normalized.max(1.0)).is_ok() { |
| 44 | + total_imported += 1; |
| 45 | + } |
| 46 | + } |
| 20 | 47 | |
| 21 | 48 | if total_imported > 0 { |
| 22 | 49 | db.save()?; |
| 23 | | - println!("Imported {} total entries", total_imported); |
| 24 | | - } else { |
| 25 | | - println!("No databases found to import from"); |
| 50 | + println!("Imported {} entries (scores normalized to 1-{})", total_imported, MAX_IMPORT_SCORE as u32); |
| 26 | 51 | } |
| 27 | 52 | |
| 28 | 53 | Ok(()) |
| 29 | 54 | } |
| 30 | 55 | |
| 31 | | -/// Import from zoxide using its CLI. |
| 32 | | -fn import_zoxide(db: &mut Database) -> Result<usize> { |
| 56 | +/// Collect entries from zoxide using its CLI. |
| 57 | +fn collect_zoxide(entries: &mut Vec<(String, f64)>) -> Result<()> { |
| 33 | 58 | let output = Command::new("zoxide") |
| 34 | 59 | .args(["query", "--list", "--score"]) |
| 35 | 60 | .stdout(Stdio::piped()) |
@@ -38,36 +63,37 @@ fn import_zoxide(db: &mut Database) -> Result<usize> { |
| 38 | 63 | |
| 39 | 64 | let output = match output { |
| 40 | 65 | Ok(o) if o.status.success() => o, |
| 41 | | - _ => return Ok(0), |
| 66 | + _ => return Ok(()), |
| 42 | 67 | }; |
| 43 | 68 | |
| 44 | | - let mut imported = 0; |
| 45 | 69 | let reader = BufReader::new(output.stdout.as_slice()); |
| 70 | + let mut count = 0; |
| 46 | 71 | |
| 47 | 72 | for line in reader.lines() { |
| 48 | 73 | let line = line?; |
| 49 | 74 | if let Some((score, path)) = parse_score_path(&line) { |
| 50 | | - if db.import_entry(path, score).is_ok() { |
| 51 | | - imported += 1; |
| 75 | + if std::path::Path::new(path).exists() { |
| 76 | + entries.push((path.to_string(), score)); |
| 77 | + count += 1; |
| 52 | 78 | } |
| 53 | 79 | } |
| 54 | 80 | } |
| 55 | 81 | |
| 56 | | - if imported > 0 { |
| 57 | | - println!(" zoxide: {} entries", imported); |
| 82 | + if count > 0 { |
| 83 | + println!(" zoxide: {} entries", count); |
| 58 | 84 | } |
| 59 | | - Ok(imported) |
| 85 | + Ok(()) |
| 60 | 86 | } |
| 61 | 87 | |
| 62 | | -/// Import from autojump (~/.local/share/autojump/autojump.txt). |
| 63 | | -fn import_autojump(db: &mut Database) -> Result<usize> { |
| 88 | +/// Collect entries from autojump (~/.local/share/autojump/autojump.txt). |
| 89 | +fn collect_autojump(entries: &mut Vec<(String, f64)>) -> Result<()> { |
| 64 | 90 | let path = dirs_autojump(); |
| 65 | 91 | if !path.exists() { |
| 66 | | - return Ok(0); |
| 92 | + return Ok(()); |
| 67 | 93 | } |
| 68 | 94 | |
| 69 | 95 | let content = fs::read_to_string(&path)?; |
| 70 | | - let mut imported = 0; |
| 96 | + let mut count = 0; |
| 71 | 97 | |
| 72 | 98 | // Format: "score\tpath" (tab-separated) |
| 73 | 99 | for line in content.lines() { |
@@ -76,23 +102,24 @@ fn import_autojump(db: &mut Database) -> Result<usize> { |
| 76 | 102 | continue; |
| 77 | 103 | } |
| 78 | 104 | |
| 79 | | - if let Some((score_str, path)) = line.split_once('\t') { |
| 105 | + if let Some((score_str, dir_path)) = line.split_once('\t') { |
| 80 | 106 | if let Ok(score) = score_str.parse::<f64>() { |
| 81 | | - if db.import_entry(path, score).is_ok() { |
| 82 | | - imported += 1; |
| 107 | + if std::path::Path::new(dir_path).exists() { |
| 108 | + entries.push((dir_path.to_string(), score)); |
| 109 | + count += 1; |
| 83 | 110 | } |
| 84 | 111 | } |
| 85 | 112 | } |
| 86 | 113 | } |
| 87 | 114 | |
| 88 | | - if imported > 0 { |
| 89 | | - println!(" autojump: {} entries", imported); |
| 115 | + if count > 0 { |
| 116 | + println!(" autojump: {} entries", count); |
| 90 | 117 | } |
| 91 | | - Ok(imported) |
| 118 | + Ok(()) |
| 92 | 119 | } |
| 93 | 120 | |
| 94 | | -/// Import from z/z.lua/zsh-z (~/.z). |
| 95 | | -fn import_z(db: &mut Database) -> Result<usize> { |
| 121 | +/// Collect entries from z/z.lua/zsh-z (~/.z). |
| 122 | +fn collect_z(entries: &mut Vec<(String, f64)>) -> Result<()> { |
| 96 | 123 | // Check both ~/.z and $Z_DATA / $_Z_DATA |
| 97 | 124 | let paths: Vec<PathBuf> = [ |
| 98 | 125 | Some(dirs_z()), |
@@ -104,7 +131,7 @@ fn import_z(db: &mut Database) -> Result<usize> { |
| 104 | 131 | .flatten() |
| 105 | 132 | .collect(); |
| 106 | 133 | |
| 107 | | - let mut imported = 0; |
| 134 | + let mut count = 0; |
| 108 | 135 | |
| 109 | 136 | for path in paths { |
| 110 | 137 | if !path.exists() { |
@@ -125,31 +152,32 @@ fn import_z(db: &mut Database) -> Result<usize> { |
| 125 | 152 | |
| 126 | 153 | let parts: Vec<&str> = line.split('|').collect(); |
| 127 | 154 | if parts.len() >= 2 { |
| 128 | | - let path = parts[0]; |
| 155 | + let dir_path = parts[0]; |
| 129 | 156 | if let Ok(score) = parts[1].parse::<f64>() { |
| 130 | | - if db.import_entry(path, score).is_ok() { |
| 131 | | - imported += 1; |
| 157 | + if std::path::Path::new(dir_path).exists() { |
| 158 | + entries.push((dir_path.to_string(), score)); |
| 159 | + count += 1; |
| 132 | 160 | } |
| 133 | 161 | } |
| 134 | 162 | } |
| 135 | 163 | } |
| 136 | 164 | } |
| 137 | 165 | |
| 138 | | - if imported > 0 { |
| 139 | | - println!(" z/z.lua: {} entries", imported); |
| 166 | + if count > 0 { |
| 167 | + println!(" z/z.lua: {} entries", count); |
| 140 | 168 | } |
| 141 | | - Ok(imported) |
| 169 | + Ok(()) |
| 142 | 170 | } |
| 143 | 171 | |
| 144 | | -/// Import from fasd (~/.fasd). |
| 145 | | -fn import_fasd(db: &mut Database) -> Result<usize> { |
| 172 | +/// Collect entries from fasd (~/.fasd). |
| 173 | +fn collect_fasd(entries: &mut Vec<(String, f64)>) -> Result<()> { |
| 146 | 174 | let path = dirs_fasd(); |
| 147 | 175 | if !path.exists() { |
| 148 | | - return Ok(0); |
| 176 | + return Ok(()); |
| 149 | 177 | } |
| 150 | 178 | |
| 151 | 179 | let content = fs::read_to_string(&path)?; |
| 152 | | - let mut imported = 0; |
| 180 | + let mut count = 0; |
| 153 | 181 | |
| 154 | 182 | // Format: "path|score|timestamp" (similar to z) |
| 155 | 183 | for line in content.lines() { |
@@ -160,22 +188,21 @@ fn import_fasd(db: &mut Database) -> Result<usize> { |
| 160 | 188 | |
| 161 | 189 | let parts: Vec<&str> = line.split('|').collect(); |
| 162 | 190 | if parts.len() >= 2 { |
| 163 | | - let path = parts[0]; |
| 191 | + let dir_path = parts[0]; |
| 164 | 192 | if let Ok(score) = parts[1].parse::<f64>() { |
| 165 | 193 | // fasd tracks files too, only import directories |
| 166 | | - if std::path::Path::new(path).is_dir() { |
| 167 | | - if db.import_entry(path, score).is_ok() { |
| 168 | | - imported += 1; |
| 169 | | - } |
| 194 | + if std::path::Path::new(dir_path).is_dir() { |
| 195 | + entries.push((dir_path.to_string(), score)); |
| 196 | + count += 1; |
| 170 | 197 | } |
| 171 | 198 | } |
| 172 | 199 | } |
| 173 | 200 | } |
| 174 | 201 | |
| 175 | | - if imported > 0 { |
| 176 | | - println!(" fasd: {} entries", imported); |
| 202 | + if count > 0 { |
| 203 | + println!(" fasd: {} entries", count); |
| 177 | 204 | } |
| 178 | | - Ok(imported) |
| 205 | + Ok(()) |
| 179 | 206 | } |
| 180 | 207 | |
| 181 | 208 | /// Parse " 123.4 /path/to/dir" format (zoxide output). |