Rust · 5545 bytes Raw Blame History
1 use std::fs;
2 use std::io::{BufRead, BufReader};
3 use std::path::PathBuf;
4 use std::process::{Command, Stdio};
5
6 use crate::db::Database;
7
8 use super::Result;
9
10 /// Import directories from various tools (zoxide, autojump, z, fasd).
11 pub fn run() -> Result<()> {
12 let mut db = Database::open()?;
13 let mut total_imported = 0;
14
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)?;
20
21 if total_imported > 0 {
22 db.save()?;
23 println!("Imported {} total entries", total_imported);
24 } else {
25 println!("No databases found to import from");
26 }
27
28 Ok(())
29 }
30
31 /// Import from zoxide using its CLI.
32 fn import_zoxide(db: &mut Database) -> Result<usize> {
33 let output = Command::new("zoxide")
34 .args(["query", "--list", "--score"])
35 .stdout(Stdio::piped())
36 .stderr(Stdio::null())
37 .output();
38
39 let output = match output {
40 Ok(o) if o.status.success() => o,
41 _ => return Ok(0),
42 };
43
44 let mut imported = 0;
45 let reader = BufReader::new(output.stdout.as_slice());
46
47 for line in reader.lines() {
48 let line = line?;
49 if let Some((score, path)) = parse_score_path(&line) {
50 if db.import_entry(path, score).is_ok() {
51 imported += 1;
52 }
53 }
54 }
55
56 if imported > 0 {
57 println!(" zoxide: {} entries", imported);
58 }
59 Ok(imported)
60 }
61
62 /// Import from autojump (~/.local/share/autojump/autojump.txt).
63 fn import_autojump(db: &mut Database) -> Result<usize> {
64 let path = dirs_autojump();
65 if !path.exists() {
66 return Ok(0);
67 }
68
69 let content = fs::read_to_string(&path)?;
70 let mut imported = 0;
71
72 // Format: "score\tpath" (tab-separated)
73 for line in content.lines() {
74 let line = line.trim();
75 if line.is_empty() {
76 continue;
77 }
78
79 if let Some((score_str, path)) = line.split_once('\t') {
80 if let Ok(score) = score_str.parse::<f64>() {
81 if db.import_entry(path, score).is_ok() {
82 imported += 1;
83 }
84 }
85 }
86 }
87
88 if imported > 0 {
89 println!(" autojump: {} entries", imported);
90 }
91 Ok(imported)
92 }
93
94 /// Import from z/z.lua/zsh-z (~/.z).
95 fn import_z(db: &mut Database) -> Result<usize> {
96 // Check both ~/.z and $Z_DATA / $_Z_DATA
97 let paths: Vec<PathBuf> = [
98 Some(dirs_z()),
99 std::env::var("_Z_DATA").map(PathBuf::from).ok(),
100 std::env::var("Z_DATA").map(PathBuf::from).ok(),
101 std::env::var("ZSHZ_DATA").map(PathBuf::from).ok(),
102 ]
103 .into_iter()
104 .flatten()
105 .collect();
106
107 let mut imported = 0;
108
109 for path in paths {
110 if !path.exists() {
111 continue;
112 }
113
114 let content = match fs::read_to_string(&path) {
115 Ok(c) => c,
116 Err(_) => continue,
117 };
118
119 // Format: "path|score|timestamp" (pipe-separated)
120 for line in content.lines() {
121 let line = line.trim();
122 if line.is_empty() {
123 continue;
124 }
125
126 let parts: Vec<&str> = line.split('|').collect();
127 if parts.len() >= 2 {
128 let path = parts[0];
129 if let Ok(score) = parts[1].parse::<f64>() {
130 if db.import_entry(path, score).is_ok() {
131 imported += 1;
132 }
133 }
134 }
135 }
136 }
137
138 if imported > 0 {
139 println!(" z/z.lua: {} entries", imported);
140 }
141 Ok(imported)
142 }
143
144 /// Import from fasd (~/.fasd).
145 fn import_fasd(db: &mut Database) -> Result<usize> {
146 let path = dirs_fasd();
147 if !path.exists() {
148 return Ok(0);
149 }
150
151 let content = fs::read_to_string(&path)?;
152 let mut imported = 0;
153
154 // Format: "path|score|timestamp" (similar to z)
155 for line in content.lines() {
156 let line = line.trim();
157 if line.is_empty() {
158 continue;
159 }
160
161 let parts: Vec<&str> = line.split('|').collect();
162 if parts.len() >= 2 {
163 let path = parts[0];
164 if let Ok(score) = parts[1].parse::<f64>() {
165 // 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 }
170 }
171 }
172 }
173 }
174
175 if imported > 0 {
176 println!(" fasd: {} entries", imported);
177 }
178 Ok(imported)
179 }
180
181 /// Parse " 123.4 /path/to/dir" format (zoxide output).
182 fn parse_score_path(line: &str) -> Option<(f64, &str)> {
183 let line = line.trim_start();
184 let space_idx = line.find(' ')?;
185 let score_str = &line[..space_idx];
186 let path = line[space_idx..].trim_start();
187
188 if path.is_empty() {
189 return None;
190 }
191
192 let score = score_str.parse().ok()?;
193 Some((score, path))
194 }
195
196 fn dirs_autojump() -> PathBuf {
197 if let Some(data) = dirs::data_local_dir() {
198 data.join("autojump").join("autojump.txt")
199 } else {
200 PathBuf::from("~/.local/share/autojump/autojump.txt")
201 }
202 }
203
204 fn dirs_z() -> PathBuf {
205 dirs::home_dir()
206 .map(|h| h.join(".z"))
207 .unwrap_or_else(|| PathBuf::from("~/.z"))
208 }
209
210 fn dirs_fasd() -> PathBuf {
211 dirs::home_dir()
212 .map(|h| h.join(".fasd"))
213 .unwrap_or_else(|| PathBuf::from("~/.fasd"))
214 }
215