implement zoxide import command
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
8007a1be8839964a7b7156149a9e9b18c1bf3c87- Parents
-
782e20d - Tree
d2b3c58
8007a1b
8007a1be8839964a7b7156149a9e9b18c1bf3c87782e20d
d2b3c58| Status | File | + | - |
|---|---|---|---|
| M |
src/cmd/import.rs
|
82 | 6 |
| M |
src/db/entry.rs
|
9 | 0 |
| M |
src/db/store.rs
|
27 | 0 |
src/cmd/import.rsmodified@@ -1,11 +1,87 @@ | ||
| 1 | +use std::io::{BufRead, BufReader}; | |
| 2 | +use std::process::{Command, Stdio}; | |
| 3 | + | |
| 4 | +use crate::db::Database; | |
| 5 | + | |
| 1 | 6 | use super::{CmdError, Result}; |
| 2 | 7 | |
| 3 | 8 | /// Import directories from zoxide database. |
| 4 | -/// | |
| 5 | -/// This is a placeholder - full implementation will be in Sprint 4. | |
| 6 | 9 | pub fn run() -> Result<()> { |
| 7 | - // TODO: Sprint 4 - implement zoxide import | |
| 8 | - Err(CmdError::Other( | |
| 9 | - "Import not yet implemented (Sprint 4)".to_string(), | |
| 10 | - )) | |
| 10 | + // Try to get entries from zoxide CLI | |
| 11 | + let output = Command::new("zoxide") | |
| 12 | + .args(["query", "--list", "--score"]) | |
| 13 | + .stdout(Stdio::piped()) | |
| 14 | + .stderr(Stdio::null()) | |
| 15 | + .output(); | |
| 16 | + | |
| 17 | + let output = match output { | |
| 18 | + Ok(o) if o.status.success() => o, | |
| 19 | + Ok(_) => { | |
| 20 | + return Err(CmdError::Other( | |
| 21 | + "zoxide query failed - is zoxide installed and initialized?".to_string(), | |
| 22 | + )); | |
| 23 | + } | |
| 24 | + Err(_) => { | |
| 25 | + return Err(CmdError::Other( | |
| 26 | + "zoxide not found - please install zoxide first".to_string(), | |
| 27 | + )); | |
| 28 | + } | |
| 29 | + }; | |
| 30 | + | |
| 31 | + let mut db = Database::open()?; | |
| 32 | + let mut imported = 0; | |
| 33 | + let mut skipped = 0; | |
| 34 | + | |
| 35 | + let reader = BufReader::new(output.stdout.as_slice()); | |
| 36 | + for line in reader.lines() { | |
| 37 | + let line = line?; | |
| 38 | + let line = line.trim(); | |
| 39 | + | |
| 40 | + if line.is_empty() { | |
| 41 | + continue; | |
| 42 | + } | |
| 43 | + | |
| 44 | + // Parse " 123.4 /path/to/dir" format | |
| 45 | + if let Some((score_str, path)) = parse_zoxide_line(line) { | |
| 46 | + let score: f64 = match score_str.parse() { | |
| 47 | + Ok(s) => s, | |
| 48 | + Err(_) => { | |
| 49 | + skipped += 1; | |
| 50 | + continue; | |
| 51 | + } | |
| 52 | + }; | |
| 53 | + | |
| 54 | + // Import with the zoxide score | |
| 55 | + if let Err(_) = db.import_entry(path, score) { | |
| 56 | + skipped += 1; | |
| 57 | + } else { | |
| 58 | + imported += 1; | |
| 59 | + } | |
| 60 | + } else { | |
| 61 | + skipped += 1; | |
| 62 | + } | |
| 63 | + } | |
| 64 | + | |
| 65 | + db.save()?; | |
| 66 | + | |
| 67 | + println!("Imported {} entries from zoxide", imported); | |
| 68 | + if skipped > 0 { | |
| 69 | + println!("Skipped {} entries (parse errors or excluded paths)", skipped); | |
| 70 | + } | |
| 71 | + | |
| 72 | + Ok(()) | |
| 73 | +} | |
| 74 | + | |
| 75 | +/// Parse a zoxide output line: " 123.4 /path/to/dir" | |
| 76 | +fn parse_zoxide_line(line: &str) -> Option<(&str, &str)> { | |
| 77 | + let line = line.trim_start(); | |
| 78 | + let space_idx = line.find(' ')?; | |
| 79 | + let score = &line[..space_idx]; | |
| 80 | + let path = line[space_idx..].trim_start(); | |
| 81 | + | |
| 82 | + if path.is_empty() { | |
| 83 | + return None; | |
| 84 | + } | |
| 85 | + | |
| 86 | + Some((score, path)) | |
| 11 | 87 | } |
src/db/entry.rsmodified@@ -24,6 +24,15 @@ impl DirEntry { | ||
| 24 | 24 | } |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | + /// Create an entry with a specific score (for imports). | |
| 28 | + pub fn with_score(score: f64) -> Self { | |
| 29 | + Self { | |
| 30 | + score, | |
| 31 | + last_accessed: Utc::now(), | |
| 32 | + access_count: 1, | |
| 33 | + } | |
| 34 | + } | |
| 35 | + | |
| 27 | 36 | /// Record an access to this directory, updating score and timestamp. |
| 28 | 37 | pub fn record_access(&mut self) { |
| 29 | 38 | self.score += 1.0; |
src/db/store.rsmodified@@ -160,6 +160,33 @@ impl Database { | ||
| 160 | 160 | Ok(()) |
| 161 | 161 | } |
| 162 | 162 | |
| 163 | + /// Import an entry with a specific score (for importing from zoxide). | |
| 164 | + pub fn import_entry<P: AsRef<Path>>(&mut self, path: P, score: f64) -> Result<(), DatabaseError> { | |
| 165 | + let path = PathBuf::from(path.as_ref()); | |
| 166 | + | |
| 167 | + // Skip if excluded | |
| 168 | + if self.is_excluded(&path) { | |
| 169 | + return Ok(()); | |
| 170 | + } | |
| 171 | + | |
| 172 | + // Only import if path exists | |
| 173 | + if !path.exists() { | |
| 174 | + return Ok(()); | |
| 175 | + } | |
| 176 | + | |
| 177 | + // Add or merge with existing entry | |
| 178 | + if let Some(entry) = self.data.entries.get_mut(&path) { | |
| 179 | + // Merge scores (take the higher one) | |
| 180 | + if score > entry.score { | |
| 181 | + entry.score = score; | |
| 182 | + } | |
| 183 | + } else { | |
| 184 | + self.data.entries.insert(path, DirEntry::with_score(score)); | |
| 185 | + } | |
| 186 | + | |
| 187 | + Ok(()) | |
| 188 | + } | |
| 189 | + | |
| 163 | 190 | /// Remove a directory from the database. |
| 164 | 191 | pub fn remove<P: AsRef<Path>>(&mut self, path: P) -> Result<(), DatabaseError> { |
| 165 | 192 | let path = self.canonicalize_path(path)?; |