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::().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); } } }