From 5d709bded501b0688d3b798f121f0bbabbd46846 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Thu, 26 Mar 2020 00:53:04 +0530 Subject: [PATCH 01/16] Remove i32 conversion from fzf helper --- src/util.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/util.rs b/src/util.rs index c223a96..53d016b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -45,15 +45,8 @@ pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result> { for dir in dirs.iter() { // ensure that frecency fits in 4 characters - let frecency = if dir.rank > 9999.0 { - 9999 - } else if dir.rank > 0.0 { - dir.rank as i32 - } else { - 0 - }; - - writeln!(fzf_stdin, "{:>4} {}", frecency, dir.path) + let frecency = clamp(dir.rank, 0.0, 9999.0); + writeln!(fzf_stdin, "{:>4.0} {}", frecency, dir.path) .with_context(|| anyhow!("could not write into fzf stdin"))?; } @@ -89,3 +82,18 @@ pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result> { _ => bail!("fzf returned an unknown error"), } } + +// FIXME: replace with f64::clamp once it is stable +#[must_use = "method returns a new number and does not mutate the original value"] +#[inline] +pub fn clamp(val: f64, min: f64, max: f64) -> f64 { + assert!(min <= max); + let mut x = val; + if x < min { + x = min; + } + if x > max { + x = max; + } + x +} From 857e15c65f383a6a41707712e8ccb4641ae28d2d Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Thu, 26 Mar 2020 01:13:14 +0530 Subject: [PATCH 02/16] Remove conversion to i64 when sorting --- src/util.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/util.rs b/src/util.rs index 53d016b..e4a537f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,6 +3,7 @@ use crate::dir::Dir; use crate::env::Env; use crate::types::Epoch; use anyhow::{anyhow, bail, Context, Result}; +use std::cmp::{Ordering, PartialOrd}; use std::io::{Read, Write}; use std::process::{Command, Stdio}; use std::time::SystemTime; @@ -41,7 +42,12 @@ pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result> { dir.rank = dir.get_frecency(now); } - dirs.sort_by_key(|dir| std::cmp::Reverse(dir.rank as i64)); + dirs.sort_unstable_by(|dir1, dir2| { + dir1.rank + .partial_cmp(&dir2.rank) + .unwrap_or(Ordering::Equal) + .reverse() + }); for dir in dirs.iter() { // ensure that frecency fits in 4 characters From 890185176e14510d420c1e484c024d1bc5038141 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Fri, 27 Mar 2020 17:11:26 +0530 Subject: [PATCH 03/16] Store paths as PathBuf rather than String --- Cargo.lock | 135 ++++++++++++++++++++++++++++---------- Cargo.toml | 10 ++- src/db.rs | 82 +++++++++-------------- src/dir.rs | 41 ++++++++---- src/main.rs | 3 +- src/subcommand/add.rs | 7 +- src/subcommand/migrate.rs | 1 + src/subcommand/query.rs | 31 ++++++--- src/subcommand/remove.rs | 1 + src/util.rs | 43 ++++++++---- 10 files changed, 223 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c8ebbe..7e40c16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -29,7 +29,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -49,7 +49,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -67,6 +67,16 @@ dependencies = [ "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "byteorder" version = "1.3.4" @@ -121,7 +131,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -131,7 +141,7 @@ name = "envy" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -140,7 +150,7 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -157,7 +167,28 @@ name = "hermit-abi" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indoc" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indoc-impl 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indoc-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -167,33 +198,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.67" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro-error" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "1.0.9" @@ -225,6 +266,14 @@ dependencies = [ "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rust-argon2" version = "0.7.0" @@ -238,20 +287,20 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -261,29 +310,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -298,7 +347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -324,6 +373,11 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unindent" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -362,18 +416,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "zoxide" version = "0.2.2" dependencies = [ - "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "anyhow 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "envy 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +"checksum anyhow 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785" "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" @@ -382,6 +438,7 @@ dependencies = [ "checksum bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +"checksum bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" @@ -393,26 +450,32 @@ dependencies = [ "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +"checksum indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79255cf29f5711995ddf9ec261b4057b1deb34e66c90656c201e41376872c544" +"checksum indoc-impl 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "54554010aa3d17754e484005ea0022f1c93839aabc627c2c55f3d7b47206134c" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" -"checksum proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a" -"checksum proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a" +"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +"checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +"checksum proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" "checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" -"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" -"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +"checksum serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)" = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" +"checksum serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)" = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073" -"checksum structopt-derive 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd" -"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +"checksum structopt 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d" +"checksum structopt-derive 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430" +"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/Cargo.toml b/Cargo.toml index 40d270c..e31e3a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,17 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.26" +anyhow = "1.0.27" bincode = "1.2.1" clap = "2.33.0" dirs = "2.0.2" envy = "0.4.1" -serde = { version = "1.0.104", features = ["derive"] } -structopt = "0.3.11" +indoc = "0.3.5" +serde = { version = "1.0.105", features = ["derive"] } +structopt = "0.3.12" + +[target.'cfg(unix)'.dependencies] +bstr = "0.2.12" [profile.release] codegen-units = 1 diff --git a/src/db.rs b/src/db.rs index 6e5badb..a3806a0 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,6 +1,8 @@ use crate::dir::Dir; use crate::types::{Epoch, Rank}; + use anyhow::{anyhow, bail, Context, Result}; +use indoc::indoc; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, BufWriter}; use std::path::{Path, PathBuf}; @@ -18,12 +20,11 @@ impl DB { let dirs = match File::open(&path) { Ok(file) => { let reader = BufReader::new(&file); - bincode::deserialize_from(reader) - .with_context(|| anyhow!("could not deserialize database"))? + bincode::deserialize_from(reader).context("could not deserialize database")? } Err(err) => match err.kind() { io::ErrorKind::NotFound => Vec::::new(), - _ => return Err(err).with_context(|| anyhow!("could not open database file")), + _ => return Err(err).context("could not open database file"), }, }; @@ -38,15 +39,13 @@ impl DB { if self.modified { let path_tmp = self.get_path_tmp(); - let file_tmp = File::create(&path_tmp) - .with_context(|| anyhow!("could not open temporary database file"))?; + let file_tmp = + File::create(&path_tmp).context("could not open temporary database file")?; let writer = BufWriter::new(&file_tmp); - bincode::serialize_into(writer, &self.dirs) - .with_context(|| anyhow!("could not serialize database"))?; + bincode::serialize_into(writer, &self.dirs).context("could not serialize database")?; - fs::rename(&path_tmp, &self.path) - .with_context(|| anyhow!("could not move temporary database file"))?; + fs::rename(&path_tmp, &self.path).context("could not move temporary database file")?; } Ok(()) @@ -54,14 +53,13 @@ impl DB { pub fn migrate>(&mut self, path: P, merge: bool) -> Result<()> { if !self.dirs.is_empty() && !merge { - bail!( + bail!(indoc!( "To prevent conflicts, you can only migrate from z with an empty zoxide database! -If you wish to merge the two, specify the `--merge` flag." - ); + If you wish to merge the two, specify the `--merge` flag." + )); } - let z_db_file = - File::open(path).with_context(|| anyhow!("could not open z database file"))?; + let z_db_file = File::open(path).context("could not open z database file")?; let reader = BufReader::new(z_db_file); for (idx, read_line) in reader.lines().enumerate() { @@ -104,22 +102,11 @@ If you wish to merge the two, specify the `--merge` flag." continue; } }; - let path_str = match path_abs.to_str() { - Some(path) => path, - None => { - eprintln!( - "invalid unicode in path '{}' at line {}", - path_abs.display(), - line_number - ); - continue; - } - }; if merge { // If the path exists in the database, add the ranks and set the epoch to // the largest of the parsed epoch and the already present epoch. - if let Some(dir) = self.dirs.iter_mut().find(|dir| dir.path == path_str) { + if let Some(dir) = self.dirs.iter_mut().find(|dir| dir.path == path_abs) { dir.rank += rank; dir.last_accessed = Epoch::max(epoch, dir.last_accessed); @@ -130,7 +117,7 @@ If you wish to merge the two, specify the `--merge` flag." // FIXME: When we switch to PathBuf for storing directories inside Dir, just // pass `PathBuf::from(path_str)` self.dirs.push(Dir { - path: path_str.to_string(), + path: path_abs, rank, last_accessed: epoch, }); @@ -154,13 +141,9 @@ If you wish to merge the two, specify the `--merge` flag." .canonicalize() .with_context(|| anyhow!("could not access directory: {}", path.as_ref().display()))?; - let path_str = path_abs - .to_str() - .ok_or_else(|| anyhow!("invalid unicode in path: {}", path_abs.display()))?; - - match self.dirs.iter_mut().find(|dir| dir.path == path_str) { + match self.dirs.iter_mut().find(|dir| dir.path == path_abs) { None => self.dirs.push(Dir { - path: path_str.to_string(), + path: path_abs, last_accessed: now, rank: 1.0, }), @@ -186,20 +169,19 @@ If you wish to merge the two, specify the `--merge` flag." } pub fn query(&mut self, keywords: &[String], now: Epoch) -> Option { - loop { - let (idx, dir) = self - .dirs - .iter() - .enumerate() - .filter(|(_, dir)| dir.is_match(keywords)) - .max_by_key(|(_, dir)| dir.get_frecency(now) as i64)?; + let (idx, dir) = self + .dirs + .iter() + .enumerate() + .filter(|(_, dir)| dir.is_match(&keywords)) + .max_by_key(|(_, dir)| dir.get_frecency(now) as i64)?; - if dir.is_dir() { - return Some(dir.to_owned()); - } else { - self.dirs.remove(idx); - self.modified = true; - } + if dir.is_dir() { + Some(dir.to_owned()) + } else { + self.dirs.swap_remove(idx); + self.modified = true; + self.query(keywords, now) } } @@ -219,12 +201,8 @@ If you wish to merge the two, specify the `--merge` flag." Err(_) => path.as_ref().to_path_buf(), }; - let path_str = path_abs - .to_str() - .ok_or_else(|| anyhow!("invalid unicode in path"))?; - - if let Some(idx) = self.dirs.iter().position(|dir| dir.path == path_str) { - self.dirs.remove(idx); + if let Some(idx) = self.dirs.iter().position(|dir| dir.path == path_abs) { + self.dirs.swap_remove(idx); self.modified = true; } diff --git a/src/dir.rs b/src/dir.rs index 5a5f97b..c24d74a 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,36 +1,49 @@ use crate::types::{Epoch, Rank}; + use serde::{Deserialize, Serialize}; -use std::path::Path; +use std::path::PathBuf; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Dir { - pub path: String, + pub path: PathBuf, pub rank: Rank, pub last_accessed: Epoch, } impl Dir { pub fn is_dir(&self) -> bool { - Path::new(&self.path).is_dir() + self.path.is_dir() } + #[cfg(unix)] pub fn is_match(&self, query: &[String]) -> bool { - let path = self.path.to_ascii_lowercase(); + use bstr::ByteSlice; + use std::os::unix::ffi::OsStrExt; - if let Some(query_name) = query.last().and_then(|word| Path::new(word).file_name()) { - if let Some(path_name) = Path::new(&path).file_name() { - // `unwrap()` here should be safe because the values are already encoded as UTF-8 - let query_name = query_name.to_str().unwrap(); - let path_name = path_name.to_str().unwrap(); + let path_bytes = self.path.as_os_str().as_bytes().to_lowercase(); + let mut subpath = path_bytes.as_slice(); - if !path_name.contains(&query_name) { - return false; - } + for subquery in query.iter() { + let subquery_bytes = subquery.as_bytes(); + match subpath.find(subquery_bytes) { + Some(idx) => subpath = &subpath[idx + subquery_bytes.len()..], + None => return false, } } - let mut subpath = path.as_str(); - for subquery in query { + true + } + + #[cfg(not(unix))] + pub fn is_match(&self, query: &[String]) -> bool { + let path_str = match self.path.to_str() { + Some(path_str) => path_str.to_lowercase(), + None => return false, // silently ignore invalid UTF-8 + }; + + let mut subpath = path_str.as_str(); + + for subquery in query.iter() { match subpath.find(subquery) { Some(idx) => subpath = &subpath[idx + subquery.len()..], None => return false, diff --git a/src/main.rs b/src/main.rs index 8d64a43..de5d4c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod types; mod util; use crate::env::Env; + use anyhow::{Context, Result}; use structopt::StructOpt; @@ -23,7 +24,7 @@ pub fn main() -> Result<()> { let opt = Zoxide::from_args(); let env = envy::prefixed("_ZO_") .from_env::() - .with_context(|| "could not parse environment variables")?; + .context("could not parse environment variables")?; match opt { Zoxide::Add(add) => add.run(&env)?, diff --git a/src/subcommand/add.rs b/src/subcommand/add.rs index 4904307..35160fb 100644 --- a/src/subcommand/add.rs +++ b/src/subcommand/add.rs @@ -1,7 +1,8 @@ use crate::env::Env; use crate::types::Rank; use crate::util; -use anyhow::{anyhow, Context, Result}; + +use anyhow::{Context, Result}; use std::env; use structopt::StructOpt; @@ -20,8 +21,8 @@ impl Add { match &self.path { Some(path) => db.add(path, maxage, now), None => { - let current_dir = env::current_dir() - .with_context(|| anyhow!("unable to fetch current directory"))?; + let current_dir = + env::current_dir().context("unable to fetch current directory")?; db.add(current_dir, maxage, now) } } diff --git a/src/subcommand/migrate.rs b/src/subcommand/migrate.rs index c3e67ec..55c5d45 100644 --- a/src/subcommand/migrate.rs +++ b/src/subcommand/migrate.rs @@ -1,5 +1,6 @@ use crate::env::Env; use crate::util; + use anyhow::Result; use structopt::StructOpt; diff --git a/src/subcommand/query.rs b/src/subcommand/query.rs index e5cc4f9..eea7c51 100644 --- a/src/subcommand/query.rs +++ b/src/subcommand/query.rs @@ -1,6 +1,8 @@ use crate::env::Env; use crate::util; + use anyhow::{bail, Result}; +use std::io::{self, Write}; use std::path::Path; use structopt::StructOpt; @@ -21,38 +23,47 @@ impl Query { }; match path_opt { - Some(path) => println!("query: {}", path.trim()), + Some(path) => { + let stdout = io::stdout(); + let mut handle = stdout.lock(); + handle.write_all(b"query: ").unwrap(); + handle.write_all(&path).unwrap(); + handle.write_all(b"\n").unwrap(); + } None => bail!("no match found"), }; Ok(()) } - fn query(&mut self, env: &Env) -> Result> { + fn query(&mut self, env: &Env) -> Result>> { if let [path] = self.keywords.as_slice() { if Path::new(path).is_dir() { - return Ok(Some(path.to_string())); + return Ok(Some(path.as_bytes().to_vec())); } } - for keyword in &mut self.keywords { - keyword.make_ascii_lowercase(); - } - let now = util::get_current_time()?; + for keyword in &mut self.keywords { + *keyword = keyword.to_lowercase(); + } + if let Some(dir) = util::get_db(env)?.query(&self.keywords, now) { - Ok(Some(dir.path)) + // `path_to_bytes` is guaranteed to succeed here since + // the path has already been queried successfully + let path_bytes = util::path_to_bytes(&dir.path).unwrap(); + Ok(Some(path_bytes.to_vec())) } else { Ok(None) } } - fn query_interactive(&mut self, env: &Env) -> Result> { + fn query_interactive(&mut self, env: &Env) -> Result>> { let now = util::get_current_time()?; for keyword in &mut self.keywords { - keyword.make_ascii_lowercase(); + *keyword = keyword.to_lowercase(); } let dirs = util::get_db(env)?.query_all(&self.keywords); diff --git a/src/subcommand/remove.rs b/src/subcommand/remove.rs index 7ed4c54..c5611a9 100644 --- a/src/subcommand/remove.rs +++ b/src/subcommand/remove.rs @@ -1,5 +1,6 @@ use crate::env::Env; use crate::util; + use anyhow::Result; use structopt::StructOpt; diff --git a/src/util.rs b/src/util.rs index e4a537f..c00f7e6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,12 +2,25 @@ use crate::db::DB; use crate::dir::Dir; use crate::env::Env; use crate::types::Epoch; + use anyhow::{anyhow, bail, Context, Result}; use std::cmp::{Ordering, PartialOrd}; use std::io::{Read, Write}; +use std::path::Path; use std::process::{Command, Stdio}; use std::time::SystemTime; +#[cfg(unix)] +pub fn path_to_bytes>(path: &P) -> Option<&[u8]> { + use std::os::unix::ffi::OsStrExt; + Some(path.as_ref().as_os_str().as_bytes()) +} + +#[cfg(not(unix))] +pub fn path_to_bytes>(path: &P) -> Option<&[u8]> { + Some(path.as_ref().to_str()?.as_bytes()) +} + pub fn get_db(env: &Env) -> Result { let path = env .data @@ -19,19 +32,19 @@ pub fn get_db(env: &Env) -> Result { pub fn get_current_time() -> Result { let current_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .with_context(|| "system clock set to invalid time")? + .context("system clock set to invalid time")? .as_secs(); Ok(current_time as Epoch) } -pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result> { +pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result>> { let mut fzf = Command::new("fzf") .arg("-n2..") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() - .with_context(|| anyhow!("could not launch fzf"))?; + .context("could not launch fzf")?; let fzf_stdin = fzf .stdin @@ -52,8 +65,15 @@ pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result> { for dir in dirs.iter() { // ensure that frecency fits in 4 characters let frecency = clamp(dir.rank, 0.0, 9999.0); - writeln!(fzf_stdin, "{:>4.0} {}", frecency, dir.path) - .with_context(|| anyhow!("could not write into fzf stdin"))?; + + if let Some(path_bytes) = path_to_bytes(&dir.path) { + (|| { + write!(fzf_stdin, "{:>4.0} ", frecency)?; + fzf_stdin.write_all(path_bytes)?; + fzf_stdin.write_all(b"\n") + })() + .context("could not write into fzf stdin")?; + } } let fzf_stdout = fzf @@ -61,17 +81,16 @@ pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result> { .as_mut() .ok_or_else(|| anyhow!("could not connect to fzf stdout"))?; - let mut output = String::new(); + let mut buffer = Vec::new(); fzf_stdout - .read_to_string(&mut output) - .with_context(|| anyhow!("could not read from fzf stdout"))?; - - let status = fzf.wait().with_context(|| "could not wait on fzf")?; + .read_to_end(&mut buffer) + .context("could not read from fzf stdout")?; + let status = fzf.wait().context("wait failed on fzf")?; match status.code() { // normal exit - Some(0) => match output.get(12..) { - Some(path) => Ok(Some(path.to_string())), + Some(0) => match buffer.get(12..buffer.len() - 1) { + Some(path) => Ok(Some(path.to_vec())), None => bail!("fzf returned invalid output"), }, From 76dfaad9a1dbace9bb9ef3f6c297a53143348385 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Fri, 27 Mar 2020 19:19:29 +0530 Subject: [PATCH 04/16] Set max DB size to 8 MiB --- src/db.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/db.rs b/src/db.rs index a3806a0..eba07e8 100644 --- a/src/db.rs +++ b/src/db.rs @@ -20,7 +20,10 @@ impl DB { let dirs = match File::open(&path) { Ok(file) => { let reader = BufReader::new(&file); - bincode::deserialize_from(reader).context("could not deserialize database")? + bincode::config() + .limit(8 * 1024 * 1024) // only databases upto 8 MiB are supported + .deserialize_from(reader) + .context("could not deserialize database")? } Err(err) => match err.kind() { io::ErrorKind::NotFound => Vec::::new(), From 5547fb4b802b2837d2750d28d5c942c63a36c5de Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Fri, 27 Mar 2020 19:25:37 +0530 Subject: [PATCH 05/16] Rename `migrate` command to `import` --- README.md | 4 ++-- src/db.rs | 4 ++-- src/main.rs | 4 ++-- src/subcommand/{migrate.rs => import.rs} | 8 ++++---- src/subcommand/mod.rs | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) rename src/subcommand/{migrate.rs => import.rs} (66%) diff --git a/README.md b/README.md index a57665d..6a3dbf5 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,10 @@ If you want the interactive fuzzy selection feature, you will also need to insta ### Step 2: Adding `zoxide` to your shell -If you currently use `z`, `z.lua`, or `zsh-z`, you may want to first migrate your existing database to `zoxide`: +If you currently use `z`, `z.lua`, or `zsh-z`, you may want to first import your existing database into `zoxide`: ```sh -zoxide migrate /path/to/db +zoxide import /path/to/db ``` #### zsh diff --git a/src/db.rs b/src/db.rs index eba07e8..e4973c8 100644 --- a/src/db.rs +++ b/src/db.rs @@ -54,10 +54,10 @@ impl DB { Ok(()) } - pub fn migrate>(&mut self, path: P, merge: bool) -> Result<()> { + pub fn import>(&mut self, path: P, merge: bool) -> Result<()> { if !self.dirs.is_empty() && !merge { bail!(indoc!( - "To prevent conflicts, you can only migrate from z with an empty zoxide database! + "To prevent conflicts, you can only import from z with an empty zoxide database! If you wish to merge the two, specify the `--merge` flag." )); } diff --git a/src/main.rs b/src/main.rs index de5d4c7..7e8e681 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,8 @@ use structopt::StructOpt; #[structopt(about = "A cd command that learns your habits")] enum Zoxide { Add(subcommand::Add), + Import(subcommand::Import), Init(subcommand::Init), - Migrate(subcommand::Migrate), Query(subcommand::Query), Remove(subcommand::Remove), } @@ -28,8 +28,8 @@ pub fn main() -> Result<()> { match opt { Zoxide::Add(add) => add.run(&env)?, + Zoxide::Import(import) => import.run(&env)?, Zoxide::Init(init) => init.run()?, - Zoxide::Migrate(migrate) => migrate.run(&env)?, Zoxide::Query(query) => query.run(&env)?, Zoxide::Remove(remove) => remove.run(&env)?, }; diff --git a/src/subcommand/migrate.rs b/src/subcommand/import.rs similarity index 66% rename from src/subcommand/migrate.rs rename to src/subcommand/import.rs index 55c5d45..db2926a 100644 --- a/src/subcommand/migrate.rs +++ b/src/subcommand/import.rs @@ -5,16 +5,16 @@ use anyhow::Result; use structopt::StructOpt; #[derive(Debug, StructOpt)] -#[structopt(about = "Migrate from z database")] -pub struct Migrate { +#[structopt(about = "Import from z database")] +pub struct Import { path: String, #[structopt(long, help = "Merge entries into existing database")] merge: bool, } -impl Migrate { +impl Import { pub fn run(&self, env: &Env) -> Result<()> { - util::get_db(env)?.migrate(&self.path, self.merge) + util::get_db(env)?.import(&self.path, self.merge) } } diff --git a/src/subcommand/mod.rs b/src/subcommand/mod.rs index 0aae3cf..6ca5818 100644 --- a/src/subcommand/mod.rs +++ b/src/subcommand/mod.rs @@ -1,11 +1,11 @@ mod add; +mod import; mod init; -mod migrate; mod query; mod remove; pub use add::Add; +pub use import::Import; pub use init::Init; -pub use migrate::Migrate; pub use query::Query; pub use remove::Remove; From d4fb1a05cfed6b8e0ed1d79f231d2e6f7dc735a0 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Sat, 28 Mar 2020 00:38:36 +0530 Subject: [PATCH 06/16] Manually parse environment variables --- Cargo.lock | 10 ---------- Cargo.toml | 1 - src/config.rs | 41 ++++++++++++++++++++++++++++++++++++++++ src/db.rs | 6 +++--- src/env.rs | 21 -------------------- src/main.rs | 17 ++++++----------- src/subcommand/add.rs | 9 ++++----- src/subcommand/import.rs | 5 ++--- src/subcommand/query.rs | 15 +++++++-------- src/subcommand/remove.rs | 5 ++--- src/util.rs | 10 ++++------ 11 files changed, 69 insertions(+), 71 deletions(-) create mode 100644 src/config.rs delete mode 100644 src/env.rs diff --git a/Cargo.lock b/Cargo.lock index 7e40c16..a83395e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,14 +136,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "envy" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "getrandom" version = "0.1.14" @@ -421,7 +413,6 @@ dependencies = [ "bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "envy 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -446,7 +437,6 @@ dependencies = [ "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" -"checksum envy 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f938a4abd5b75fe3737902dbc2e79ca142cc1526827a9e40b829a086758531a9" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" diff --git a/Cargo.toml b/Cargo.toml index e31e3a3..8e495ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ anyhow = "1.0.27" bincode = "1.2.1" clap = "2.33.0" dirs = "2.0.2" -envy = "0.4.1" indoc = "0.3.5" serde = { version = "1.0.105", features = ["derive"] } structopt = "0.3.12" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..cdde196 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,41 @@ +use crate::types::Rank; + +use anyhow::{bail, Context, Result}; +use std::env; +use std::fs; +use std::path::PathBuf; + +pub fn zo_data() -> Result { + let path = match env::var_os("_ZO_DATA") { + Some(data_osstr) => PathBuf::from(data_osstr), + None => { + if let Some(mut cache_dir) = dirs::cache_dir() { + cache_dir.push("zoxide"); + cache_dir + } else if let Some(mut home_dir) = dirs::home_dir() { + home_dir.push(".zoxide"); + home_dir + } else { + bail!("could not generate default directory, please set _ZO_DATA manually"); + } + } + }; + + fs::create_dir_all(&path).context("could not create _ZO_DATA directory")?; + Ok(path) +} + +pub fn zo_maxage() -> Result { + match env::var_os("_ZO_MAXAGE") { + Some(maxage_osstr) => match maxage_osstr.to_str() { + Some(maxage_str) => { + let maxage = maxage_str + .parse::() + .context("unable to parse _ZO_MAXAGE as integer")?; + Ok(maxage as Rank) + } + None => bail!("invalid Unicode in _ZO_MAXAGE"), + }, + None => Ok(1000.0), + } +} diff --git a/src/db.rs b/src/db.rs index e4973c8..ec01efb 100644 --- a/src/db.rs +++ b/src/db.rs @@ -163,11 +163,11 @@ impl DB { for dir in &mut self.dirs { dir.rank *= factor; } + + self.dirs.retain(|dir| dir.rank >= 1.0); } - self.dirs.retain(|dir| dir.rank >= 1.0); self.modified = true; - Ok(()) } @@ -214,7 +214,7 @@ impl DB { fn get_path_tmp(&self) -> PathBuf { let mut path_tmp = self.path.clone(); - path_tmp.set_file_name(".zo.tmp"); + path_tmp.set_file_name("db.zo.tmp"); path_tmp } diff --git a/src/env.rs b/src/env.rs deleted file mode 100644 index ec974f8..0000000 --- a/src/env.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::Deserialize; -use std::path::PathBuf; - -#[derive(Deserialize, Debug)] -pub struct Env { - #[serde(default = "default_maxage")] - pub maxage: i64, - - #[serde(default = "default_data")] - pub data: Option, -} - -fn default_maxage() -> i64 { - 1000 -} - -fn default_data() -> Option { - let mut path = dirs::home_dir()?; - path.push(".zo"); - Some(path) -} diff --git a/src/main.rs b/src/main.rs index 7e8e681..f1c2766 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ +mod config; mod db; mod dir; -mod env; mod subcommand; mod types; mod util; -use crate::env::Env; - -use anyhow::{Context, Result}; +use anyhow::Result; use structopt::StructOpt; #[derive(Debug, StructOpt)] @@ -22,16 +20,13 @@ enum Zoxide { pub fn main() -> Result<()> { let opt = Zoxide::from_args(); - let env = envy::prefixed("_ZO_") - .from_env::() - .context("could not parse environment variables")?; match opt { - Zoxide::Add(add) => add.run(&env)?, - Zoxide::Import(import) => import.run(&env)?, + Zoxide::Add(add) => add.run()?, + Zoxide::Import(import) => import.run()?, Zoxide::Init(init) => init.run()?, - Zoxide::Query(query) => query.run(&env)?, - Zoxide::Remove(remove) => remove.run(&env)?, + Zoxide::Query(query) => query.run()?, + Zoxide::Remove(remove) => remove.run()?, }; Ok(()) diff --git a/src/subcommand/add.rs b/src/subcommand/add.rs index 35160fb..71550ee 100644 --- a/src/subcommand/add.rs +++ b/src/subcommand/add.rs @@ -1,5 +1,4 @@ -use crate::env::Env; -use crate::types::Rank; +use crate::config; use crate::util; use anyhow::{Context, Result}; @@ -13,10 +12,10 @@ pub struct Add { } impl Add { - pub fn run(&self, env: &Env) -> Result<()> { - let mut db = util::get_db(env)?; + pub fn run(&self) -> Result<()> { + let mut db = util::get_db()?; let now = util::get_current_time()?; - let maxage = env.maxage as Rank; + let maxage = config::zo_maxage()?; match &self.path { Some(path) => db.add(path, maxage, now), diff --git a/src/subcommand/import.rs b/src/subcommand/import.rs index db2926a..f691afd 100644 --- a/src/subcommand/import.rs +++ b/src/subcommand/import.rs @@ -1,4 +1,3 @@ -use crate::env::Env; use crate::util; use anyhow::Result; @@ -14,7 +13,7 @@ pub struct Import { } impl Import { - pub fn run(&self, env: &Env) -> Result<()> { - util::get_db(env)?.import(&self.path, self.merge) + pub fn run(&self) -> Result<()> { + util::get_db()?.import(&self.path, self.merge) } } diff --git a/src/subcommand/query.rs b/src/subcommand/query.rs index eea7c51..4bae377 100644 --- a/src/subcommand/query.rs +++ b/src/subcommand/query.rs @@ -1,4 +1,3 @@ -use crate::env::Env; use crate::util; use anyhow::{bail, Result}; @@ -15,11 +14,11 @@ pub struct Query { } impl Query { - pub fn run(mut self, env: &Env) -> Result<()> { + pub fn run(mut self) -> Result<()> { let path_opt = if self.interactive { - self.query_interactive(env)? + self.query_interactive()? } else { - self.query(env)? + self.query()? }; match path_opt { @@ -36,7 +35,7 @@ impl Query { Ok(()) } - fn query(&mut self, env: &Env) -> Result>> { + fn query(&mut self) -> Result>> { if let [path] = self.keywords.as_slice() { if Path::new(path).is_dir() { return Ok(Some(path.as_bytes().to_vec())); @@ -49,7 +48,7 @@ impl Query { *keyword = keyword.to_lowercase(); } - if let Some(dir) = util::get_db(env)?.query(&self.keywords, now) { + if let Some(dir) = util::get_db()?.query(&self.keywords, now) { // `path_to_bytes` is guaranteed to succeed here since // the path has already been queried successfully let path_bytes = util::path_to_bytes(&dir.path).unwrap(); @@ -59,14 +58,14 @@ impl Query { } } - fn query_interactive(&mut self, env: &Env) -> Result>> { + fn query_interactive(&mut self) -> Result>> { let now = util::get_current_time()?; for keyword in &mut self.keywords { *keyword = keyword.to_lowercase(); } - let dirs = util::get_db(env)?.query_all(&self.keywords); + let dirs = util::get_db()?.query_all(&self.keywords); util::fzf_helper(now, dirs) } } diff --git a/src/subcommand/remove.rs b/src/subcommand/remove.rs index c5611a9..b115c28 100644 --- a/src/subcommand/remove.rs +++ b/src/subcommand/remove.rs @@ -1,4 +1,3 @@ -use crate::env::Env; use crate::util; use anyhow::Result; @@ -11,7 +10,7 @@ pub struct Remove { } impl Remove { - pub fn run(&self, env: &Env) -> Result<()> { - util::get_db(env)?.remove(&self.path) + pub fn run(&self) -> Result<()> { + util::get_db()?.remove(&self.path) } } diff --git a/src/util.rs b/src/util.rs index c00f7e6..b6615ce 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ +use crate::config; use crate::db::DB; use crate::dir::Dir; -use crate::env::Env; use crate::types::Epoch; use anyhow::{anyhow, bail, Context, Result}; @@ -21,11 +21,9 @@ pub fn path_to_bytes>(path: &P) -> Option<&[u8]> { Some(path.as_ref().to_str()?.as_bytes()) } -pub fn get_db(env: &Env) -> Result { - let path = env - .data - .as_ref() - .ok_or_else(|| anyhow!("could not locate database file"))?; +pub fn get_db() -> Result { + let mut path = config::zo_data()?; + path.push("db.zo"); DB::open(path) } From f29b642ffc6f97bf5d4ce466fc09b05bbed66a54 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Sat, 28 Mar 2020 00:43:33 +0530 Subject: [PATCH 07/16] Remove FIXME --- src/db.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/db.rs b/src/db.rs index ec01efb..dbb9d3e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -117,8 +117,6 @@ impl DB { }; } - // FIXME: When we switch to PathBuf for storing directories inside Dir, just - // pass `PathBuf::from(path_str)` self.dirs.push(Dir { path: path_abs, rank, From 11901068490bae0384255af1e56d120515a4d1ba Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 27 Mar 2020 14:56:48 -0700 Subject: [PATCH 08/16] Implement _ZO_EXCLUDE_DIRS _ZO_EXCLUDE_DIRS is a list of paths (separated by colons, `:`, on Unix-based systems, and semicolons, `;`, on Windows) that should be excluded from the database. Example: _ZO_EXCLUDE_DIRS="$HOME:$HOME/something/super/secret:$HOME/caused/by/background/cds" --- src/config.rs | 8 ++++++++ src/subcommand/add.rs | 23 ++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/config.rs b/src/config.rs index cdde196..39a8ceb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use crate::types::Rank; use anyhow::{bail, Context, Result}; + use std::env; use std::fs; use std::path::PathBuf; @@ -25,6 +26,13 @@ pub fn zo_data() -> Result { Ok(path) } +pub fn zo_exclude_dirs() -> Vec { + match env::var_os("_ZO_EXCLUDE_DIRS") { + Some(dirs_osstr) => env::split_paths(&dirs_osstr).collect(), + None => Vec::new(), + } +} + pub fn zo_maxage() -> Result { match env::var_os("_ZO_MAXAGE") { Some(maxage_osstr) => match maxage_osstr.to_str() { diff --git a/src/subcommand/add.rs b/src/subcommand/add.rs index 71550ee..c3c4b22 100644 --- a/src/subcommand/add.rs +++ b/src/subcommand/add.rs @@ -2,13 +2,15 @@ use crate::config; use crate::util; use anyhow::{Context, Result}; -use std::env; use structopt::StructOpt; +use std::env; +use std::path::PathBuf; + #[derive(Debug, StructOpt)] #[structopt(about = "Add a new directory or increment its rank")] pub struct Add { - path: Option, + path: Option, } impl Add { @@ -16,14 +18,17 @@ impl Add { let mut db = util::get_db()?; let now = util::get_current_time()?; let maxage = config::zo_maxage()?; + let excluded_dirs = config::zo_exclude_dirs(); - match &self.path { - Some(path) => db.add(path, maxage, now), - None => { - let current_dir = - env::current_dir().context("unable to fetch current directory")?; - db.add(current_dir, maxage, now) - } + let path = match &self.path { + Some(path) => path.clone(), + None => env::current_dir().context("unable to fetch current directory")?, + }; + + if excluded_dirs.contains(&path) { + return Ok(()); } + + db.add(path, maxage, now) } } From 91cced7333879ebe1e14138b91960f20037ada19 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 27 Mar 2020 23:06:25 -0700 Subject: [PATCH 09/16] Document _ZO_EXCLUDE_DIRS env var --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a3dbf5..9cbe0e6 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,9 @@ NOTE: There is no PWD hook provided for POSIX shells. ### Environment variables -- `$_ZO_ECHO`: `z` will print the matched directory before navigating to it - `$_ZO_DATA`: sets the location of the database (default: `~/.zo`) +- `$_ZO_ECHO`: `z` will print the matched directory before navigating to it +- `$_ZO_EXCLUDE_DIRS`: list of directories separated by platform-specific + characters (`:` on Linux and macOS, and `;` on Windows) to be excluded from + the database - `$_ZO_MAXAGE`: sets the maximum total rank after which entries start getting deleted From 057ed96c0a7770fe9dd06b15d120fa1a3bdb6f91 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Sat, 28 Mar 2020 23:51:46 +0530 Subject: [PATCH 10/16] Refactor DB architecture --- src/config.rs | 7 ++- src/db.rs | 130 ++++++++++++++++++++++++++++----------- src/dir.rs | 5 +- src/main.rs | 1 - src/subcommand/import.rs | 4 +- src/subcommand/query.rs | 3 +- src/subcommand/remove.rs | 4 +- src/types.rs | 2 - src/util.rs | 4 +- 9 files changed, 113 insertions(+), 47 deletions(-) delete mode 100644 src/types.rs diff --git a/src/config.rs b/src/config.rs index 39a8ceb..6e41b1b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ -use crate::types::Rank; +use crate::db::DBVersion; +use crate::dir::Rank; use anyhow::{bail, Context, Result}; @@ -6,6 +7,10 @@ use std::env; use std::fs; use std::path::PathBuf; +pub const DB_MAX_SIZE: u64 = 8 * 1024 * 1024; // 8 MiB + +pub const DB_VERSION: DBVersion = 3; + pub fn zo_data() -> Result { let path = match env::var_os("_ZO_DATA") { Some(data_osstr) => PathBuf::from(data_osstr), diff --git a/src/db.rs b/src/db.rs index dbb9d3e..32d3f2a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,40 +1,75 @@ -use crate::dir::Dir; -use crate::types::{Epoch, Rank}; +use crate::config; +use crate::dir::{Dir, Epoch, Rank}; use anyhow::{anyhow, bail, Context, Result}; use indoc::indoc; +use serde::{Deserialize, Serialize}; + +use std::cmp::Ordering; use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, BufWriter}; use std::path::{Path, PathBuf}; +pub use i32 as DBVersion; + pub struct DB { - path: PathBuf, - dirs: Vec, + data: DBData, modified: bool, + path: PathBuf, + path_old: Option, } impl DB { pub fn open>(path: P) -> Result { - let path = path.as_ref().to_path_buf(); - - let dirs = match File::open(&path) { + let data = match File::open(&path) { Ok(file) => { let reader = BufReader::new(&file); bincode::config() - .limit(8 * 1024 * 1024) // only databases upto 8 MiB are supported + .limit(config::DB_MAX_SIZE) .deserialize_from(reader) .context("could not deserialize database")? } Err(err) => match err.kind() { - io::ErrorKind::NotFound => Vec::::new(), + io::ErrorKind::NotFound => DBData::default(), _ => return Err(err).context("could not open database file"), }, }; + if data.version != config::DB_VERSION { + bail!("this database version ({}) is unsupported", data.version); + } + Ok(DB { - path, - dirs, + data, modified: false, + path: path.as_ref().to_path_buf(), + path_old: None, + }) + } + + pub fn open_old(path_old: P1, path: P2) -> Result + where + P1: AsRef, + P2: AsRef, + { + let file = File::open(&path_old).context("could not open old database file")?; + let reader = BufReader::new(&file); + + let dirs = bincode::config() + .limit(config::DB_MAX_SIZE) + .deserialize_from(reader) + .context("could not deserialize old database")?; + + let data = DBData { + version: config::DB_VERSION, + dirs, + }; + + Ok(DB { + data, + modified: true, + path: path.as_ref().to_path_buf(), + path_old: Some(path_old.as_ref().to_path_buf()), }) } @@ -46,7 +81,8 @@ impl DB { File::create(&path_tmp).context("could not open temporary database file")?; let writer = BufWriter::new(&file_tmp); - bincode::serialize_into(writer, &self.dirs).context("could not serialize database")?; + bincode::serialize_into(writer, &self.data) + .context("could not serialize database")?; fs::rename(&path_tmp, &self.path).context("could not move temporary database file")?; } @@ -55,7 +91,7 @@ impl DB { } pub fn import>(&mut self, path: P, merge: bool) -> Result<()> { - if !self.dirs.is_empty() && !merge { + if !self.data.dirs.is_empty() && !merge { bail!(indoc!( "To prevent conflicts, you can only import from z with an empty zoxide database! If you wish to merge the two, specify the `--merge` flag." @@ -109,7 +145,9 @@ impl DB { if merge { // If the path exists in the database, add the ranks and set the epoch to // the largest of the parsed epoch and the already present epoch. - if let Some(dir) = self.dirs.iter_mut().find(|dir| dir.path == path_abs) { + if let Some(dir) = + self.data.dirs.iter_mut().find(|dir| dir.path == path_abs) + { dir.rank += rank; dir.last_accessed = Epoch::max(epoch, dir.last_accessed); @@ -117,7 +155,7 @@ impl DB { }; } - self.dirs.push(Dir { + self.data.dirs.push(Dir { path: path_abs, rank, last_accessed: epoch, @@ -142,8 +180,8 @@ impl DB { .canonicalize() .with_context(|| anyhow!("could not access directory: {}", path.as_ref().display()))?; - match self.dirs.iter_mut().find(|dir| dir.path == path_abs) { - None => self.dirs.push(Dir { + match self.data.dirs.iter_mut().find(|dir| dir.path == path_abs) { + None => self.data.dirs.push(Dir { path: path_abs, last_accessed: now, rank: 1.0, @@ -154,15 +192,15 @@ impl DB { } }; - let sum_age = self.dirs.iter().map(|dir| dir.rank).sum::(); + let sum_age = self.data.dirs.iter().map(|dir| dir.rank).sum::(); if sum_age > max_age { let factor = 0.9 * max_age / sum_age; - for dir in &mut self.dirs { + for dir in &mut self.data.dirs { dir.rank *= factor; } - self.dirs.retain(|dir| dir.rank >= 1.0); + self.data.dirs.retain(|dir| dir.rank >= 1.0); } self.modified = true; @@ -170,26 +208,36 @@ impl DB { } pub fn query(&mut self, keywords: &[String], now: Epoch) -> Option { - let (idx, dir) = self + let (idx, dir, _) = self + .data .dirs .iter() .enumerate() .filter(|(_, dir)| dir.is_match(&keywords)) - .max_by_key(|(_, dir)| dir.get_frecency(now) as i64)?; + .map(|(idx, dir)| (idx, dir, dir.get_frecency(now))) + .max_by(|(_, _, frecency1), (_, _, frecency2)| { + frecency1.partial_cmp(frecency2).unwrap_or(Ordering::Equal) + })?; if dir.is_dir() { Some(dir.to_owned()) } else { - self.dirs.swap_remove(idx); + self.data.dirs.swap_remove(idx); self.modified = true; self.query(keywords, now) } } pub fn query_all(&mut self, keywords: &[String]) -> Vec { - self.remove_invalid(); + let orig_len = self.data.dirs.len(); + self.data.dirs.retain(Dir::is_dir); - self.dirs + if orig_len != self.data.dirs.len() { + self.modified = true; + } + + self.data + .dirs .iter() .filter(|dir| dir.is_match(&keywords)) .cloned() @@ -202,8 +250,8 @@ impl DB { Err(_) => path.as_ref().to_path_buf(), }; - if let Some(idx) = self.dirs.iter().position(|dir| dir.path == path_abs) { - self.dirs.swap_remove(idx); + if let Some(idx) = self.data.dirs.iter().position(|dir| dir.path == path_abs) { + self.data.dirs.swap_remove(idx); self.modified = true; } @@ -215,21 +263,31 @@ impl DB { path_tmp.set_file_name("db.zo.tmp"); path_tmp } - - fn remove_invalid(&mut self) { - let orig_len = self.dirs.len(); - self.dirs.retain(Dir::is_dir); - - if orig_len != self.dirs.len() { - self.modified = true; - } - } } impl Drop for DB { fn drop(&mut self) { if let Err(e) = self.save() { eprintln!("{:#}", e); + } else if let Some(path_old) = &self.path_old { + if let Err(e) = fs::remove_file(path_old).context("could not remove old database") { + eprintln!("{:#}", e); + } + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +struct DBData { + version: DBVersion, + dirs: Vec, +} + +impl Default for DBData { + fn default() -> DBData { + DBData { + version: config::DB_VERSION, + dirs: Vec::new(), } } } diff --git a/src/dir.rs b/src/dir.rs index c24d74a..aa7f67d 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,8 +1,9 @@ -use crate::types::{Epoch, Rank}; - use serde::{Deserialize, Serialize}; use std::path::PathBuf; +pub use f64 as Rank; +pub use i64 as Epoch; // use a signed integer so subtraction can be performed on it + #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Dir { pub path: PathBuf, diff --git a/src/main.rs b/src/main.rs index f1c2766..a480b45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ mod config; mod db; mod dir; mod subcommand; -mod types; mod util; use anyhow::Result; diff --git a/src/subcommand/import.rs b/src/subcommand/import.rs index f691afd..3e10191 100644 --- a/src/subcommand/import.rs +++ b/src/subcommand/import.rs @@ -3,10 +3,12 @@ use crate::util; use anyhow::Result; use structopt::StructOpt; +use std::path::PathBuf; + #[derive(Debug, StructOpt)] #[structopt(about = "Import from z database")] pub struct Import { - path: String, + path: PathBuf, #[structopt(long, help = "Merge entries into existing database")] merge: bool, diff --git a/src/subcommand/query.rs b/src/subcommand/query.rs index 4bae377..cba97d1 100644 --- a/src/subcommand/query.rs +++ b/src/subcommand/query.rs @@ -1,9 +1,10 @@ use crate::util; use anyhow::{bail, Result}; +use structopt::StructOpt; + use std::io::{self, Write}; use std::path::Path; -use structopt::StructOpt; #[derive(Debug, StructOpt)] #[structopt(about = "Search for a directory")] diff --git a/src/subcommand/remove.rs b/src/subcommand/remove.rs index b115c28..5ba579d 100644 --- a/src/subcommand/remove.rs +++ b/src/subcommand/remove.rs @@ -3,10 +3,12 @@ use crate::util; use anyhow::Result; use structopt::StructOpt; +use std::path::PathBuf; + #[derive(Debug, StructOpt)] #[structopt(about = "Remove a directory")] pub struct Remove { - path: String, + path: PathBuf, } impl Remove { diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 1ddffd1..0000000 --- a/src/types.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub use f64 as Rank; -pub use i64 as Epoch; // use a signed integer so subtraction can be performed on it diff --git a/src/util.rs b/src/util.rs index b6615ce..c9e4057 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,9 +1,9 @@ use crate::config; use crate::db::DB; -use crate::dir::Dir; -use crate::types::Epoch; +use crate::dir::{Dir, Epoch}; use anyhow::{anyhow, bail, Context, Result}; + use std::cmp::{Ordering, PartialOrd}; use std::io::{Read, Write}; use std::path::Path; From d4c8297f9bf416a894a1eea3721b1db12d0c785b Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Sun, 29 Mar 2020 11:55:21 +0530 Subject: [PATCH 11/16] Use UUID4 for temporary database file name --- Cargo.lock | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/db.rs | 15 ++++++++++---- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a83395e..0baa4e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -198,6 +198,11 @@ name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro-error" version = "0.4.12" @@ -243,6 +248,43 @@ dependencies = [ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "redox_syscall" version = "0.1.56" @@ -370,6 +412,14 @@ name = "unindent" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vec_map" version = "0.8.1" @@ -416,6 +466,7 @@ dependencies = [ "indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] @@ -445,11 +496,16 @@ dependencies = [ "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" "checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" "checksum proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" "checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" @@ -466,6 +522,7 @@ dependencies = [ "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" +"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/Cargo.toml b/Cargo.toml index 8e495ea..be13607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ dirs = "2.0.2" indoc = "0.3.5" serde = { version = "1.0.105", features = ["derive"] } structopt = "0.3.12" +uuid = { version = "0.8.1", features = ["v4"] } [target.'cfg(unix)'.dependencies] bstr = "0.2.12" diff --git a/src/db.rs b/src/db.rs index 32d3f2a..9712366 100644 --- a/src/db.rs +++ b/src/db.rs @@ -4,6 +4,7 @@ use crate::dir::{Dir, Epoch, Rank}; use anyhow::{anyhow, bail, Context, Result}; use indoc::indoc; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use std::cmp::Ordering; use std::fs::{self, File}; @@ -81,10 +82,13 @@ impl DB { File::create(&path_tmp).context("could not open temporary database file")?; let writer = BufWriter::new(&file_tmp); - bincode::serialize_into(writer, &self.data) - .context("could not serialize database")?; + bincode::serialize_into(writer, &self.data).context("could not serialize database")?; - fs::rename(&path_tmp, &self.path).context("could not move temporary database file")?; + if let Err(e) = fs::rename(&path_tmp, &self.path) { + fs::remove_file(&path_tmp) + .context("could not move or delete temporary database file")?; + return Err(e).context("could not move temporary database file"); + } } Ok(()) @@ -259,8 +263,11 @@ impl DB { } fn get_path_tmp(&self) -> PathBuf { + let file_name = format!(".{}.zo", Uuid::new_v4()); + let mut path_tmp = self.path.clone(); - path_tmp.set_file_name("db.zo.tmp"); + path_tmp.set_file_name(file_name); + path_tmp } } From 9af0251bd6606729f5e862d19e0c0d7404f7fd2c Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Sun, 29 Mar 2020 13:42:51 -0700 Subject: [PATCH 12/16] Rework database fallback for v0.3 (#47) Support migration from old database --- README.md | 5 ++++- src/config.rs | 24 ++++++++++++------------ src/db.rs | 2 +- src/util.rs | 18 +++++++++++++++--- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9cbe0e6..6b59dda 100644 --- a/README.md +++ b/README.md @@ -116,9 +116,12 @@ NOTE: There is no PWD hook provided for POSIX shells. ### Environment variables -- `$_ZO_DATA`: sets the location of the database (default: `~/.zo`) +- `$_ZO_DATA_DIR`: directory where `zoxide` will store its data files (default: + platform-specific; see the [`dirs` documentation] for more information) - `$_ZO_ECHO`: `z` will print the matched directory before navigating to it - `$_ZO_EXCLUDE_DIRS`: list of directories separated by platform-specific characters (`:` on Linux and macOS, and `;` on Windows) to be excluded from the database - `$_ZO_MAXAGE`: sets the maximum total rank after which entries start getting deleted + +[`dirs` documentation]: https://docs.rs/dirs/latest/dirs/fn.data_local_dir.html diff --git a/src/config.rs b/src/config.rs index 6e41b1b..2376fff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,27 +8,26 @@ use std::fs; use std::path::PathBuf; pub const DB_MAX_SIZE: u64 = 8 * 1024 * 1024; // 8 MiB - pub const DB_VERSION: DBVersion = 3; -pub fn zo_data() -> Result { - let path = match env::var_os("_ZO_DATA") { +pub fn zo_data_dir() -> Result { + let data_dir = match env::var_os("_ZO_DATA_DIR") { Some(data_osstr) => PathBuf::from(data_osstr), None => { - if let Some(mut cache_dir) = dirs::cache_dir() { - cache_dir.push("zoxide"); - cache_dir - } else if let Some(mut home_dir) = dirs::home_dir() { - home_dir.push(".zoxide"); - home_dir + if let Some(mut data_dir) = dirs::data_local_dir() { + data_dir.push("zoxide"); + data_dir } else { - bail!("could not generate default directory, please set _ZO_DATA manually"); + bail!("could not find database directory, please set _ZO_DATA_DIR manually"); } } }; - fs::create_dir_all(&path).context("could not create _ZO_DATA directory")?; - Ok(path) + // This will fail when `data_dir` points to a file or a broken symlink, but + // will no-op on a valid symlink (to a directory), or an actual directory. + fs::create_dir_all(&data_dir).context("could not create data directory")?; + + Ok(data_dir) } pub fn zo_exclude_dirs() -> Vec { @@ -45,6 +44,7 @@ pub fn zo_maxage() -> Result { let maxage = maxage_str .parse::() .context("unable to parse _ZO_MAXAGE as integer")?; + Ok(maxage as Rank) } None => bail!("invalid Unicode in _ZO_MAXAGE"), diff --git a/src/db.rs b/src/db.rs index 9712366..758865c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -48,7 +48,7 @@ impl DB { }) } - pub fn open_old(path_old: P1, path: P2) -> Result + pub fn open_and_migrate(path_old: P1, path: P2) -> Result where P1: AsRef, P2: AsRef, diff --git a/src/util.rs b/src/util.rs index c9e4057..80dcccd 100644 --- a/src/util.rs +++ b/src/util.rs @@ -22,9 +22,21 @@ pub fn path_to_bytes>(path: &P) -> Option<&[u8]> { } pub fn get_db() -> Result { - let mut path = config::zo_data()?; - path.push("db.zo"); - DB::open(path) + let mut db_path = config::zo_data_dir()?; + db_path.push("db.zo"); + + // FIXME: fallback to old database location; remove in next breaking version + if !db_path.is_file() { + if let Some(mut old_db_path) = dirs::home_dir() { + old_db_path.push(".zo"); + + if old_db_path.is_file() { + return DB::open_and_migrate(old_db_path, db_path); + } + } + } + + DB::open(db_path) } pub fn get_current_time() -> Result { From 78a3d0a3d9a2722f958a5f326270a6570dffc0f1 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Mon, 30 Mar 2020 02:22:54 +0530 Subject: [PATCH 13/16] Remove indoc dependency --- Cargo.lock | 36 ------------------------------------ Cargo.toml | 1 - src/db.rs | 9 ++++----- 3 files changed, 4 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0baa4e8..3234a79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,27 +162,6 @@ dependencies = [ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "indoc" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "indoc-impl 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "indoc-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -227,11 +206,6 @@ dependencies = [ "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "proc-macro2" version = "1.0.9" @@ -407,11 +381,6 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "unindent" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "uuid" version = "0.8.1" @@ -463,7 +432,6 @@ dependencies = [ "bstr 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -491,15 +459,12 @@ dependencies = [ "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" -"checksum indoc 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "79255cf29f5711995ddf9ec261b4057b1deb34e66c90656c201e41376872c544" -"checksum indoc-impl 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "54554010aa3d17754e484005ea0022f1c93839aabc627c2c55f3d7b47206134c" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" "checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" -"checksum proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" @@ -521,7 +486,6 @@ dependencies = [ "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum unindent 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "63f18aa3b0e35fed5a0048f029558b1518095ffe2a0a31fb87c93dece93a4993" "checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" diff --git a/Cargo.toml b/Cargo.toml index be13607..0e635aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ anyhow = "1.0.27" bincode = "1.2.1" clap = "2.33.0" dirs = "2.0.2" -indoc = "0.3.5" serde = { version = "1.0.105", features = ["derive"] } structopt = "0.3.12" uuid = { version = "0.8.1", features = ["v4"] } diff --git a/src/db.rs b/src/db.rs index 758865c..2af013c 100644 --- a/src/db.rs +++ b/src/db.rs @@ -2,7 +2,6 @@ use crate::config; use crate::dir::{Dir, Epoch, Rank}; use anyhow::{anyhow, bail, Context, Result}; -use indoc::indoc; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -96,10 +95,10 @@ impl DB { pub fn import>(&mut self, path: P, merge: bool) -> Result<()> { if !self.data.dirs.is_empty() && !merge { - bail!(indoc!( - "To prevent conflicts, you can only import from z with an empty zoxide database! - If you wish to merge the two, specify the `--merge` flag." - )); + bail!( + "To prevent conflicts, you can only import from z with an empty zoxide database!\n\ + If you wish to merge the two, specify the `--merge` flag." + ); } let z_db_file = File::open(path).context("could not open z database file")?; From 3c7d100ab1ff7805444f6f64cb2339ccb4546e16 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Mon, 30 Mar 2020 03:04:39 +0530 Subject: [PATCH 14/16] Guarantee unique file names for temporary database --- src/db.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/db.rs b/src/db.rs index 2af013c..e8f9e7e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use std::cmp::Ordering; -use std::fs::{self, File}; +use std::fs::{self, File, OpenOptions}; use std::io::{self, BufRead, BufReader, BufWriter}; use std::path::{Path, PathBuf}; @@ -77,8 +77,12 @@ impl DB { if self.modified { let path_tmp = self.get_path_tmp(); - let file_tmp = - File::create(&path_tmp).context("could not open temporary database file")?; + let file_tmp = OpenOptions::new() + .write(true) + // ensure that we are not overwriting an existing file_tmp + .create_new(true) + .open(&path_tmp) + .context("could not open temporary database file")?; let writer = BufWriter::new(&file_tmp); bincode::serialize_into(writer, &self.data).context("could not serialize database")?; From ad96db844cddcf96d498240d977d9fc5d6ea27b6 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Mon, 30 Mar 2020 05:25:56 +0530 Subject: [PATCH 15/16] Add CHANGELOG.md --- CHANGELOG.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 27 ++++++++------- 2 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6d37ff7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,93 @@ +# Changelog + +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] + +## [0.3.0] - 2020-03-30 + +### Added + +- Automatic migration from `v0.2.x` databases. +- `$_ZO_EXCLUDE_DIRS` to prevent certain directories from being added to the database. +- Support for POSIX-compliant shells. + +### Changed + +- Database location defaults to user's local data directory. +- Database schema now includes a version number. +- `migrate` subcommand renamed to `import`. + +### Fixed + +- Achieve thread safety using unique temporary database file names for each `zoxide` instance. +- Incomprehensive "could not allocate" message on database corruption. + +## [0.2.2] - 2020-03-20 + +### Fixed + +- Incorrect exit codes in `z` command on `fish`. + +### Removed + +- File locks on database. + +## [0.2.1] - 2020-03-16 + +### Added + +- `$_ZO_ECHO` to echo match before `cd`ing. +- Minimal `ranger` plugin. +- PWD hook to only update the database when the current directory is changed. +- Support for the `bash` shell. +- `migrate` subcommand to allow users to migrate from `z`. + +### Fixed + +- Interactive queries causing other open shells to hang. + +## [0.2.0] - 2020-03-11 + +### Added + +- `init` subcommand to remove dependency on shell plugin managers. +- Support for `z -` command to go to previous directory. +- `Cargo.lock` for more reproducible builds. +- Support for the `fish` shell. + +### Fixed + +- `_zoxide_precmd` overriding other precmd hooks on `zsh`. + +## [0.1.1] - 2020-03-08 + +### Added + +- Install script for Linux/macOS users. +- Aging algorithm to remove stale entries. + +### Changed + +- Database schema now uses `f64` values for rank instead of `i32`. + +### Fixed + +- Multiple hooks being added upon initializing `zoxide` multiple times. + +## [0.1.0] - 2020-03-05 + +### Added + +- GitHub Actions pipeline to build and upload releases. +- Support for the `zsh` shell. + +[0.3.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.2.2...v0.3.0 +[0.2.2]: https://github.com/ajeetdsouza/zoxide/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/ajeetdsouza/zoxide/compare/v0.1.1...v0.2.0 +[0.1.1]: https://github.com/ajeetdsouza/zoxide/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/ajeetdsouza/zoxide/commits/v0.1.0 diff --git a/README.md b/README.md index 6b59dda..516e2c6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,10 @@ A cd command that learns your habits ## Introduction -`zoxide` is a blazing fast alternative to `cd`, inspired by [`z`](https://github.com/rupa/z) and [`z.lua`](https://github.com/skywind3000/z.lua). It keeps track of the directories you use most frequently, and uses a ranking algorithm to navigate to the best match. +`zoxide` is a blazing fast alternative to `cd`, inspired by +[`z`](https://github.com/rupa/z) and [`z.lua`](https://github.com/skywind3000/z.lua). +It keeps track of the directories you use most frequently, and uses a ranking algorithm +to navigate to the best match. ## Examples @@ -56,11 +59,13 @@ Otherwise, try the install script: curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/ajeetdsouza/zoxide/master/install.sh | sh ``` -If you want the interactive fuzzy selection feature, you will also need to install [`fzf`](https://github.com/junegunn/fzf.git). +If you want the interactive fuzzy selection feature, you will also need to install +[`fzf`](https://github.com/junegunn/fzf.git). ### Step 2: Adding `zoxide` to your shell -If you currently use `z`, `z.lua`, or `zsh-z`, you may want to first import your existing database into `zoxide`: +If you currently use `z`, `z.lua`, or `zsh-z`, you may want to first import +your existing database into `zoxide`: ```sh zoxide import /path/to/db @@ -92,17 +97,17 @@ zoxide init fish | source #### POSIX -Add the following line to your shell's configuration file (or, run it manually): +Add the following line to your shell's configuration file: -``` sh +```sh eval "$(zoxide init posix)" ``` NOTE: If you modify your `PS1` at any point, you may need to re-run the above command. This is due -to the fact that we store our hook in `PS1`, in order to be evaluated every time the prompt is +to the fact that the hook is stored in `PS1`, in order to be evaluated every time the prompt is displayed. -NOTE: There is no PWD hook provided for POSIX shells. +NOTE: PWD hooks are currently not supported for POSIX shells. ## Configuration @@ -110,18 +115,18 @@ NOTE: There is no PWD hook provided for POSIX shells. - `--no-define-aliases`: don't define extra aliases like `zi`, `zq`, `za`, and `zr` - `--hook `: change the event that adds a new entry to the database (default: `prompt`) - - `none`: never add entries - this will make `zoxide` useless unless you manually configure a hook + - `none`: never add entries (this will make `zoxide` useless unless you manually configure a hook) - `prompt`: add an entry at every prompt - `pwd`: add an entry whenever you change directories ### Environment variables - `$_ZO_DATA_DIR`: directory where `zoxide` will store its data files (default: - platform-specific; see the [`dirs` documentation] for more information) + platform-specific; see the [`dirs` documentation] for more information) - `$_ZO_ECHO`: `z` will print the matched directory before navigating to it - `$_ZO_EXCLUDE_DIRS`: list of directories separated by platform-specific - characters (`:` on Linux and macOS, and `;` on Windows) to be excluded from - the database + characters ("`:`" on Linux and macOS, and "`;`" on Windows) to be excluded from + the database - `$_ZO_MAXAGE`: sets the maximum total rank after which entries start getting deleted [`dirs` documentation]: https://docs.rs/dirs/latest/dirs/fn.data_local_dir.html From 9d0222383f9c517f2bf4bbaf9971af0397c94110 Mon Sep 17 00:00:00 2001 From: Ajeet D'Souza <98ajeet@gmail.com> Date: Mon, 30 Mar 2020 06:49:41 +0530 Subject: [PATCH 16/16] Style nits --- src/config.rs | 9 ++++----- src/db.rs | 6 ++++-- src/subcommand/query.rs | 10 +++++----- src/util.rs | 15 ++++++++------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/config.rs b/src/config.rs index 2376fff..61ca135 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,14 +13,13 @@ pub const DB_VERSION: DBVersion = 3; pub fn zo_data_dir() -> Result { let data_dir = match env::var_os("_ZO_DATA_DIR") { Some(data_osstr) => PathBuf::from(data_osstr), - None => { - if let Some(mut data_dir) = dirs::data_local_dir() { + None => match dirs::data_local_dir() { + Some(mut data_dir) => { data_dir.push("zoxide"); data_dir - } else { - bail!("could not find database directory, please set _ZO_DATA_DIR manually"); } - } + None => bail!("could not find database directory, please set _ZO_DATA_DIR manually"), + }, }; // This will fail when `data_dir` points to a file or a broken symlink, but diff --git a/src/db.rs b/src/db.rs index e8f9e7e..9e7c126 100644 --- a/src/db.rs +++ b/src/db.rs @@ -16,6 +16,7 @@ pub struct DB { data: DBData, modified: bool, path: PathBuf, + // FIXME: remove after next breaking version path_old: Option, } @@ -36,7 +37,7 @@ impl DB { }; if data.version != config::DB_VERSION { - bail!("this database version ({}) is unsupported", data.version); + bail!("database version '{}' is unsupported", data.version); } Ok(DB { @@ -47,6 +48,7 @@ impl DB { }) } + // FIXME: remove after next breaking version pub fn open_and_migrate(path_old: P1, path: P2) -> Result where P1: AsRef, @@ -79,7 +81,6 @@ impl DB { let file_tmp = OpenOptions::new() .write(true) - // ensure that we are not overwriting an existing file_tmp .create_new(true) .open(&path_tmp) .context("could not open temporary database file")?; @@ -280,6 +281,7 @@ impl Drop for DB { if let Err(e) = self.save() { eprintln!("{:#}", e); } else if let Some(path_old) = &self.path_old { + // FIXME: remove this branch after next breaking release if let Err(e) = fs::remove_file(path_old).context("could not remove old database") { eprintln!("{:#}", e); } diff --git a/src/subcommand/query.rs b/src/subcommand/query.rs index cba97d1..187544c 100644 --- a/src/subcommand/query.rs +++ b/src/subcommand/query.rs @@ -49,14 +49,14 @@ impl Query { *keyword = keyword.to_lowercase(); } - if let Some(dir) = util::get_db()?.query(&self.keywords, now) { + let path_opt = util::get_db()?.query(&self.keywords, now).map(|dir| { // `path_to_bytes` is guaranteed to succeed here since // the path has already been queried successfully let path_bytes = util::path_to_bytes(&dir.path).unwrap(); - Ok(Some(path_bytes.to_vec())) - } else { - Ok(None) - } + path_bytes.to_vec() + }); + + Ok(path_opt) } fn query_interactive(&mut self) -> Result>> { diff --git a/src/util.rs b/src/util.rs index 80dcccd..74614d5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -13,6 +13,7 @@ use std::time::SystemTime; #[cfg(unix)] pub fn path_to_bytes>(path: &P) -> Option<&[u8]> { use std::os::unix::ffi::OsStrExt; + Some(path.as_ref().as_os_str().as_bytes()) } @@ -123,12 +124,12 @@ pub fn fzf_helper(now: Epoch, mut dirs: Vec) -> Result>> { #[inline] pub fn clamp(val: f64, min: f64, max: f64) -> f64 { assert!(min <= max); - let mut x = val; - if x < min { - x = min; + + if val < min { + min + } else if val > max { + max + } else { + val } - if x > max { - x = max; - } - x }