173 lines
6.0 KiB
Rust
173 lines
6.0 KiB
Rust
use std::fs;
|
|
|
|
use anyhow::{bail, Context, Result};
|
|
|
|
use crate::cmd::{Import, ImportFrom, Run};
|
|
use crate::config;
|
|
use crate::db::{Database, DatabaseFile, Dir};
|
|
|
|
impl Run for Import {
|
|
fn run(&self) -> Result<()> {
|
|
let buffer = fs::read_to_string(&self.path)
|
|
.with_context(|| format!("could not open database for importing: {}", &self.path.display()))?;
|
|
|
|
let data_dir = config::data_dir()?;
|
|
let mut db = DatabaseFile::new(data_dir);
|
|
let db = &mut db.open()?;
|
|
if !self.merge && !db.dirs.is_empty() {
|
|
bail!("current database is not empty, specify --merge to continue anyway");
|
|
}
|
|
|
|
match self.from {
|
|
ImportFrom::Autojump => from_autojump(db, &buffer),
|
|
ImportFrom::Z => from_z(db, &buffer),
|
|
}
|
|
.context("import error")?;
|
|
|
|
db.save()
|
|
}
|
|
}
|
|
|
|
fn from_autojump<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
|
|
for line in buffer.lines() {
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
let mut split = line.splitn(2, '\t');
|
|
|
|
let rank = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
|
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {}", rank))?;
|
|
// Normalize the rank using a sigmoid function. Don't import actual ranks from autojump,
|
|
// since its scoring algorithm is very different and might take a while to get normalized.
|
|
rank = sigmoid(rank);
|
|
|
|
let path = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
|
|
|
db.dirs.push(Dir { path: path.into(), rank, last_accessed: 0 });
|
|
db.modified = true;
|
|
}
|
|
|
|
if db.modified {
|
|
db.dedup();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn from_z<'a>(db: &mut Database<'a>, buffer: &'a str) -> Result<()> {
|
|
for line in buffer.lines() {
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
let mut split = line.rsplitn(3, '|');
|
|
|
|
let last_accessed = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
|
let last_accessed = last_accessed.parse().with_context(|| format!("invalid epoch: {}", last_accessed))?;
|
|
|
|
let rank = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
|
let rank = rank.parse().with_context(|| format!("invalid rank: {}", rank))?;
|
|
|
|
let path = split.next().with_context(|| format!("invalid entry: {}", line))?;
|
|
|
|
db.dirs.push(Dir { path: path.into(), rank, last_accessed });
|
|
db.modified = true;
|
|
}
|
|
|
|
if db.modified {
|
|
db.dedup();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn sigmoid(x: f64) -> f64 {
|
|
1.0 / (1.0 + (-x).exp())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::sigmoid;
|
|
use crate::db::{Database, Dir};
|
|
|
|
#[test]
|
|
fn from_autojump() {
|
|
let buffer = r#"
|
|
7.0 /baz
|
|
2.0 /foo/bar
|
|
5.0 /quux/quuz
|
|
"#;
|
|
|
|
let dirs = vec![
|
|
Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
|
|
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
|
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
|
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
|
Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
|
|
];
|
|
let data_dir = tempfile::tempdir().unwrap();
|
|
let data_dir = &data_dir.path().to_path_buf();
|
|
let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
|
|
|
|
super::from_autojump(&mut db, buffer).unwrap();
|
|
db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
|
println!("got: {:?}", &db.dirs.as_slice());
|
|
|
|
let exp = &[
|
|
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
|
|
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
|
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
|
|
Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 },
|
|
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
|
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
|
];
|
|
println!("exp: {:?}", &exp);
|
|
|
|
for (dir1, dir2) in db.dirs.iter().zip(exp) {
|
|
assert_eq!(dir1.path, dir2.path);
|
|
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
|
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn from_z() {
|
|
let buffer = r#"
|
|
/baz|7|700
|
|
/quux/quuz|4|400
|
|
/foo/bar|2|200
|
|
/quux/quuz|5|500
|
|
"#;
|
|
|
|
let dirs = vec![
|
|
Dir { path: "/quux/quuz".into(), rank: 1.0, last_accessed: 100 },
|
|
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
|
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
|
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
|
Dir { path: "/foo/bar".into(), rank: 9.0, last_accessed: 900 },
|
|
];
|
|
let data_dir = tempfile::tempdir().unwrap();
|
|
let data_dir = &data_dir.path().to_path_buf();
|
|
let mut db = Database { dirs: dirs.into(), modified: false, data_dir };
|
|
|
|
super::from_z(&mut db, buffer).unwrap();
|
|
db.dirs.sort_by(|dir1, dir2| dir1.path.cmp(&dir2.path));
|
|
println!("got: {:?}", &db.dirs.as_slice());
|
|
|
|
let exp = &[
|
|
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
|
|
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
|
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
|
|
Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 },
|
|
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
|
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
|
];
|
|
println!("exp: {:?}", &exp);
|
|
|
|
for (dir1, dir2) in db.dirs.iter().zip(exp) {
|
|
assert_eq!(dir1.path, dir2.path);
|
|
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
|
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
|
}
|
|
}
|
|
}
|