tenseleyflow/gump / 8007a1b

Browse files

implement zoxide import command

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
8007a1be8839964a7b7156149a9e9b18c1bf3c87
Parents
782e20d
Tree
d2b3c58

3 changed files

StatusFile+-
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
+
16
 use super::{CmdError, Result};
27
 
38
 /// Import directories from zoxide database.
4
-///
5
-/// This is a placeholder - full implementation will be in Sprint 4.
69
 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))
1187
 }
src/db/entry.rsmodified
@@ -24,6 +24,15 @@ impl DirEntry {
2424
         }
2525
     }
2626
 
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
+
2736
     /// Record an access to this directory, updating score and timestamp.
2837
     pub fn record_access(&mut self) {
2938
         self.score += 1.0;
src/db/store.rsmodified
@@ -160,6 +160,33 @@ impl Database {
160160
         Ok(())
161161
     }
162162
 
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
+
163190
     /// Remove a directory from the database.
164191
     pub fn remove<P: AsRef<Path>>(&mut self, path: P) -> Result<(), DatabaseError> {
165192
         let path = self.canonicalize_path(path)?;