diff --git a/CHANGELOG.md b/CHANGELOG.md index 610ac08..4a21c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Auto-generated shell completions. + +### Fixed + +- Nushell: `__zoxide_hook` should not do anything inside subshells created using `enter` + ## [0.7.0] - 2021-05-02 ### Added diff --git a/src/cmd/import.rs b/src/cmd/import.rs index 474d7d9..c1992ca 100644 --- a/src/cmd/import.rs +++ b/src/cmd/import.rs @@ -1,11 +1,13 @@ use super::Run; use crate::app::{Import, ImportFrom}; use crate::config; -use crate::import::{Autojump, Import as _, Z}; -use crate::util; +use crate::db::{Database, DatabaseFile, Dir, DirList}; -use crate::db::DatabaseFile; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; + +use std::collections::HashMap; +use std::fs; +use std::path::Path; impl Run for Import { fn run(&self) -> Result<()> { @@ -17,14 +19,112 @@ impl Run for Import { bail!("current database is not empty, specify --merge to continue anyway"); } - let resolve_symlinks = config::zo_resolve_symlinks(); match self.from { - ImportFrom::Autojump => Autojump { - resolve_symlinks, - now: util::current_time()?, - } - .import(&mut db, &self.path), - ImportFrom::Z => Z { resolve_symlinks }.import(&mut db, &self.path), + ImportFrom::Autojump => from_autojump(&mut db, &self.path), + ImportFrom::Z => from_z(&mut db, &self.path), } + .context("import error") } } + +fn from_autojump>(db: &mut Database, path: P) -> Result<()> { + let path = path.as_ref(); + let buffer = fs::read_to_string(path) + .with_context(|| format!("could not open autojump database: {}", path.display()))?; + + let mut dirs = db + .dirs + .iter() + .map(|dir| (dir.path.as_ref(), dir.clone())) + .collect::>(); + + 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 = 1.0 / (1.0 + (-rank).exp()); + + let path = split + .next() + .with_context(|| format!("invalid entry: {}", line))?; + + dirs.entry(path) + .and_modify(|dir| dir.rank += rank) + .or_insert_with(|| Dir { + path: path.to_string().into(), + rank, + last_accessed: 0, + }); + } + + db.dirs = DirList(dirs.into_iter().map(|(_, dir)| dir).collect()); + db.modified = true; + + Ok(()) +} + +fn from_z>(db: &mut Database, path: P) -> Result<()> { + let path = path.as_ref(); + let buffer = fs::read_to_string(path) + .with_context(|| format!("could not open z database: {}", path.display()))?; + + let mut dirs = db + .dirs + .iter() + .map(|dir| (dir.path.as_ref(), dir.clone())) + .collect::>(); + + 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))?; + + dirs.entry(path) + .and_modify(|dir| { + dir.rank += rank; + if last_accessed > dir.last_accessed { + dir.last_accessed = last_accessed; + } + }) + .or_insert(Dir { + path: path.to_string().into(), + rank, + last_accessed, + }); + } + + db.dirs = DirList(dirs.into_iter().map(|(_, dir)| dir).collect()); + db.modified = true; + + Ok(()) +} diff --git a/src/db/dir.rs b/src/db/dir.rs index 73ed2cd..62f3701 100644 --- a/src/db/dir.rs +++ b/src/db/dir.rs @@ -10,7 +10,7 @@ use std::fs; use std::ops::{Deref, DerefMut}; #[derive(Debug, Deserialize, Serialize)] -pub struct DirList<'a>(#[serde(borrow)] Vec>); +pub struct DirList<'a>(#[serde(borrow)] pub Vec>); impl DirList<'_> { const VERSION: u32 = 3; @@ -86,7 +86,7 @@ impl<'a> From>> for DirList<'a> { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Dir<'a> { #[serde(borrow)] pub path: Cow<'a, str>, diff --git a/src/import/autojump.rs b/src/import/autojump.rs deleted file mode 100644 index f5e31cf..0000000 --- a/src/import/autojump.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::Import; - -use crate::db::{Database, Dir, Epoch}; -use anyhow::{Context, Result}; - -use std::borrow::Cow; -use std::fs; -use std::path::Path; - -pub struct Autojump { - pub resolve_symlinks: bool, - pub now: Epoch, -} - -impl Import for Autojump { - fn import>(&self, db: &mut Database, path: P) -> Result<()> { - let path = path.as_ref(); - let buffer = fs::read_to_string(path) - .with_context(|| format!("could not open autojump database: {}", path.display()))?; - - let mut entries = Vec::new(); - for (idx, line) in buffer.lines().enumerate() { - (|| -> Result<()> { - if line.is_empty() { - return Ok(()); - } - - let (rank, path) = (|| { - let mut split = line.splitn(2, '\t'); - let rank = split.next()?; - let path = split.next()?; - Some((rank, path)) - })() - .with_context(|| format!("invalid entry: {}", line))?; - - let rank = rank - .parse::() - .with_context(|| format!("invalid rank: {}", rank))?; - - entries.push((path, rank)); - Ok(()) - })() - .with_context(|| format!("line {}: error reading from autojump database", idx + 1))?; - } - - // Don't import actual ranks from autojump, since its algorithm is - // very different, and might take a while to get normalized. - let rank_sum = entries.iter().map(|(_, rank)| rank).sum::(); - for &(path, rank) in entries.iter() { - if !db.dirs.iter().any(|dir| dir.path == path) { - db.dirs.push(Dir { - path: Cow::Owned(path.into()), - rank: rank / rank_sum, - last_accessed: self.now, - }); - db.modified = true; - } - } - - Ok(()) - } -} diff --git a/src/import/mod.rs b/src/import/mod.rs deleted file mode 100644 index a9e877b..0000000 --- a/src/import/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod autojump; -mod z; - -use crate::db::Database; -use anyhow::Result; - -use std::path::Path; - -pub use autojump::Autojump; -pub use z::Z; - -pub trait Import { - fn import>(&self, db: &mut Database, path: P) -> Result<()>; -} diff --git a/src/import/z.rs b/src/import/z.rs deleted file mode 100644 index d8bcdff..0000000 --- a/src/import/z.rs +++ /dev/null @@ -1,64 +0,0 @@ -use super::Import; - -use crate::db::{Database, Dir}; -use anyhow::{Context, Result}; - -use std::borrow::Cow; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::Path; - -pub struct Z { - pub resolve_symlinks: bool, -} - -impl Import for Z { - fn import>(&self, db: &mut Database, path: P) -> Result<()> { - let file = File::open(path).context("could not open z database")?; - let reader = BufReader::new(file); - - for (idx, line) in reader.lines().enumerate() { - (|| -> Result<()> { - let line = line?; - if line.is_empty() { - return Ok(()); - } - - let (path, rank, last_accessed) = (|| { - let mut split = line.rsplitn(3, '|'); - let last_accessed = split.next()?; - let rank = split.next()?; - let path = split.next()?; - Some((path, rank, last_accessed)) - })() - .with_context(|| format!("invalid entry: {}", line))?; - - let rank = rank - .parse() - .with_context(|| format!("invalid rank: {}", rank))?; - - let last_accessed = last_accessed - .parse() - .with_context(|| format!("invalid epoch: {}", last_accessed))?; - - match db.dirs.iter_mut().find(|dir| dir.path == path) { - Some(dir) => { - dir.rank += rank; - dir.last_accessed = dir.last_accessed.max(last_accessed); - } - None => db.dirs.push(Dir { - path: Cow::Owned(path.into()), - rank, - last_accessed, - }), - } - db.modified = true; - - Ok(()) - })() - .with_context(|| format!("line {}: error reading from z database", idx + 1))?; - } - - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs index 4a9f0c8..5eb52ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ mod config; mod db; mod error; mod fzf; -mod import; mod shell; mod util;