diff --git a/Cargo.lock b/Cargo.lock index 0840e98..7a4d9c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "anyhow" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "arrayref" version = "0.3.6" @@ -33,26 +38,6 @@ name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "backtrace" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace-sys 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", - "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)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "base64" version = "0.11.0" @@ -87,11 +72,6 @@ name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cc" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cfg-if" version = "0.1.10" @@ -146,26 +126,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "failure" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "failure_derive" -version = "0.1.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)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "fs2" version = "0.4.3" @@ -185,6 +145,14 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hermit-abi" version = "0.1.8" @@ -203,6 +171,30 @@ name = "libc" version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro-error" +version = "0.4.11" +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-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)", + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.11" +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-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-macro2" version = "1.0.9" @@ -245,11 +237,6 @@ dependencies = [ "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "serde" version = "1.0.104" @@ -273,6 +260,28 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "structopt" +version = "0.3.11" +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)", +] + +[[package]] +name = "structopt-derive" +version = "0.4.4" +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-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)", +] + [[package]] name = "syn" version = "1.0.16" @@ -284,14 +293,13 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.12.3" +name = "syn-mid" +version = "0.5.0" 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)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -302,6 +310,11 @@ dependencies = [ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-width" version = "0.1.7" @@ -317,6 +330,11 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -343,58 +361,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "zoxide" -version = "0.1.1" +version = "0.2.0" dependencies = [ + "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.2.1 (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)", - "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "fs2 0.4.3 (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)", ] [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 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" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" -"checksum backtrace-sys 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35" "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "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 byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "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" "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" "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 failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" -"checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" "checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" "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 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 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 rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "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 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 synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"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 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" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 731dbfe..da87615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zoxide" -version = "0.1.1" +version = "0.2.0" authors = ["Ajeet D'Souza <98ajeet@gmail.com>"] description = "A cd command that learns your habits" repository = "https://github.com/ajeetdsouza/zoxide/" @@ -13,12 +13,13 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.26" bincode = "1.2.1" clap = "2.33.0" dirs = "2.0.2" -failure = "0.1.7" fs2 = "0.4.3" serde = { version = "1.0.104", features = ["derive"] } +structopt = "0.3.11" [profile.release] codegen-units = 1 diff --git a/README.md b/README.md index 9a8f51f..b6afc15 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # zoxide [![crates.io](https://img.shields.io/crates/v/zoxide)](https://crates.io/crates/zoxide) +![.github/workflows/release.yml](https://github.com/ajeetdsouza/zoxide/workflows/.github/workflows/release.yml/badge.svg) A cd command that learns your habits @@ -12,6 +13,7 @@ A cd command that learns your habits - [Installing `zoxide`](#installing-zoxide) - [Adding `zoxide` to your shell](#adding-zoxide-to-your-shell) - [zsh](#zsh) + - [bash](#bash) - [fish](#fish) - [Configuration](#configuration) - [Environment variables](#environment-variables) @@ -58,52 +60,30 @@ If you want the interactive fuzzy selection feature, you will also need to insta ### Step 2: Adding `zoxide` to your shell +By default, `zoxide` defines the `z`, `zi`, `za`, `zq`, and `zr` aliases. If you'd like to go with just the barebones `z`, pass the `--no-define-aliases` flag to `zoxide init`. + #### zsh -Using [antibody](https://github.com/getantibody/antibody): +Add the following line to your `~/.zshrc`: ```sh -antibody bundle ajeetdsouza/zoxide +eval "$(zoxide init zsh)" ``` -Using [zinit](https://github.com/zdharma/zinit): +#### bash + +Add the following line to your `~/.bashrc`: ```sh -zinit light ajeetdsouza/zoxide +eval "$(zoxide init bash)" ``` -Using [antigen](https://github.com/zsh-users/antigen): - -```sh -antigen bundle ajeetdsouza/zoxide -``` - -Using [zgen](https://github.com/tarjoilija/zgen): - -```sh -zgen load ajeetdsouza/zoxide -``` - -Using [zplug](https://github.com/zplug/zplug): - -```sh -zplug "ajeetdsouza/zoxide" -``` - -If you'd rather not use a package manager, add the contents of [zoxide.plugin.zsh](zoxide.plugin.zsh) to your `.zshrc`. - #### fish -Using [fisher](https://github.com/jorgebucaran/fisher): +Add the following line to your `~/.config/fish/config.fish`: ```sh -fisher add ajeetdsouza/zoxide -``` - -Using [oh-my-fish](https://github.com/oh-my-fish/oh-my-fish): - -```sh -omf install https://github.com/ajeetdsouza/zoxide +zoxide init fish | source ``` ## Configuration diff --git a/functions/z.fish b/functions/z.fish deleted file mode 100644 index faab2b9..0000000 --- a/functions/z.fish +++ /dev/null @@ -1,22 +0,0 @@ -function z - set -l argc (count $argv) - if test $argc -gt 0 - if test $argc -eq 1 -a "$argv[1]" = "-" - cd - - commandline -f repaint - else - set _Z_RESULT (zoxide query $argv) - switch "$_Z_RESULT" - case 'query: *' - cd (string sub -s 8 -- "$_Z_RESULT") - commandline -f repaint - case '*' - echo -n "$_Z_RESULT" - end - end - else - cd ~ - commandline -f repaint - end -end - diff --git a/init.fish b/init.fish index 7e5462f..2e9cc17 100644 --- a/init.fish +++ b/init.fish @@ -1,8 +1 @@ -function zoxide-add --on-event fish_prompt - zoxide add -end - -abbr -a zi "z -i" -abbr -a za "zoxide add" -abbr -a zq "zoxide query" -abbr -a zr "zoxide remove" +zoxide init fish | source diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index e3b6b5c..0000000 --- a/src/config.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::error::AppError; -use crate::types::Rank; -use failure::{bail, ResultExt}; -use std::env; -use std::ffi::OsString; - -pub const ZO_DATA: &str = "_ZO_DATA"; -pub const ZO_MAXAGE: &str = "_ZO_MAXAGE"; - -pub fn get_zo_data() -> Result { - let path = match env::var_os(ZO_DATA) { - Some(path) => path, - None => { - let mut path = dirs::home_dir().ok_or_else(|| AppError::GetHomeDirError)?; - path.push(".zo"); - path.into_os_string() - } - }; - Ok(path) -} - -pub fn get_zo_maxage() -> Result { - if let Some(maxage_osstr) = env::var_os(ZO_MAXAGE) { - match maxage_osstr.to_str() { - Some(maxage_str) => { - let maxage = maxage_str - .parse::() - .with_context(|_| AppError::EnvError(ZO_MAXAGE.to_owned()))?; - Ok(maxage) - } - None => bail!(AppError::EnvError(ZO_MAXAGE.to_owned())), - } - } else { - Ok(5000.0) - } -} diff --git a/src/db.rs b/src/db.rs index b008b8f..0a1fbe2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,67 +1,85 @@ -use crate::config::get_zo_maxage; use crate::dir::Dir; -use crate::error::AppError; use crate::types::{Rank, Timestamp}; -use failure::ResultExt; +use crate::util::get_zo_maxage; +use anyhow::{anyhow, Context, Result}; use fs2::FileExt; -use serde::{Deserialize, Serialize}; -use std::fs::File; +use std::fs::{self, File, OpenOptions}; use std::io::{self, BufReader, BufWriter}; -use std::io::{Read, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; -#[derive(Debug, Default, Deserialize, Serialize)] pub struct DB { - pub dirs: Vec, + path: PathBuf, + path_tmp: PathBuf, - #[serde(skip)] - pub modified: bool, + file_tmp: File, + + dirs: Vec, + modified: bool, } impl DB { - pub fn open>(path: P) -> Result { - match File::open(path) { + pub fn open>(path: P) -> Result { + let path = path.as_ref().to_path_buf(); + + let mut path_tmp = path.clone(); + path_tmp.set_file_name(".zo.tmp"); + + let file_tmp = OpenOptions::new() + .write(true) + .create(true) + .open(&path_tmp) + .with_context(|| anyhow!("could not open temporary database"))?; + + file_tmp + .lock_exclusive() + .with_context(|| anyhow!("could not lock temporary database"))?; + + let dirs = match File::open(&path) { Ok(file) => { - file.lock_shared() - .with_context(|_| AppError::FileLockError)?; - let rd = BufReader::new(file); - let db = DB::read_from(rd).with_context(|_| AppError::DBReadError)?; - Ok(db) + let rd = BufReader::new(&file); + bincode::deserialize_from(rd) + .with_context(|| anyhow!("could not deserialize database"))? } Err(err) => match err.kind() { - io::ErrorKind::NotFound => Ok(DB::default()), - _ => Err(err).with_context(|_| AppError::FileOpenError)?, + io::ErrorKind::NotFound => Vec::::new(), + _ => return Err(err).with_context(|| anyhow!("could not open database")), }, - } + }; + + Ok(DB { + path, + path_tmp, + file_tmp, + dirs, + modified: false, + }) } - pub fn save>(&mut self, path: P) -> Result<(), failure::Error> { + pub fn save(&mut self) -> Result<()> { if self.modified { - let file = File::create(path).with_context(|_| AppError::FileOpenError)?; - file.lock_exclusive() - .with_context(|_| AppError::FileLockError)?; - let wr = BufWriter::new(file); - self.write_into(wr) - .with_context(|_| AppError::DBWriteError)?; + self.file_tmp + .set_len(0) + .with_context(|| "could not truncate temporary database")?; + + let wr = BufWriter::new(&self.file_tmp); + bincode::serialize_into(wr, &self.dirs) + .with_context(|| anyhow!("could not serialize database"))?; + fs::rename(&self.path_tmp, &self.path) + .with_context(|| anyhow!("could not move temporary database"))?; } Ok(()) } - pub fn read_from(rd: R) -> Result { - bincode::deserialize_from(rd) - } - - pub fn write_into(&self, wr: W) -> Result<(), bincode::Error> { - bincode::serialize_into(wr, &self) - } - - pub fn add>(&mut self, path: P, now: Timestamp) -> Result<(), failure::Error> { + pub fn add>(&mut self, path: P, now: Timestamp) -> Result<()> { let path_abs = path .as_ref() .canonicalize() - .with_context(|_| AppError::PathAccessError)?; - let path_str = path_abs.to_str().ok_or_else(|| AppError::UnicodeError)?; + .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) { None => self.dirs.push(Dir { @@ -92,8 +110,6 @@ impl DB { } pub fn query(&mut self, keywords: &[String], now: Timestamp) -> Option { - // TODO: expand "~" in queries - loop { let (idx, dir) = self .dirs @@ -111,12 +127,29 @@ impl DB { } } - pub fn remove>(&mut self, path: P) -> Result<(), failure::Error> { - let path_abs = path - .as_ref() - .canonicalize() - .with_context(|_| AppError::PathAccessError)?; - let path_str = path_abs.to_str().ok_or_else(|| AppError::UnicodeError)?; + pub fn query_all(&mut self, mut keywords: Vec) -> Vec { + self.remove_invalid(); + + for keyword in &mut keywords { + keyword.make_ascii_lowercase(); + } + + self.dirs + .iter() + .filter(|dir| dir.is_match(&keywords)) + .cloned() + .collect() + } + + pub fn remove>(&mut self, path: P) -> Result<()> { + let path_abs = match path.as_ref().canonicalize() { + Ok(path_abs) => path_abs, + 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); @@ -126,11 +159,10 @@ impl DB { Ok(()) } - pub fn remove_invalid(&mut self) { - let dirs_len = self.dirs.len(); - self.dirs.retain(|dir| dir.is_dir()); - - if self.dirs.len() != dirs_len { + 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; } } diff --git a/src/dir.rs b/src/dir.rs index bfcac92..12664c9 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -2,7 +2,7 @@ use crate::types::{Rank, Timestamp}; use serde::{Deserialize, Serialize}; use std::path::Path; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Dir { pub path: String, pub rank: Rank, @@ -17,16 +17,14 @@ impl Dir { pub fn is_match(&self, query: &[String]) -> bool { let path = self.path.to_ascii_lowercase(); - if let Some(dir_name) = Path::new(&path).file_name() { - if let Some(query_last) = query.last() { - if let Some(query_dir_name) = Path::new(query_last).file_name() { - // `unwrap()` here should be safe because the values are already encoded as UTF-8 - let dir_name_str = dir_name.to_str().unwrap().to_ascii_lowercase(); - let query_dir_name_str = query_dir_name.to_str().unwrap().to_ascii_lowercase(); + 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(); - if !dir_name_str.contains(&query_dir_name_str) { - return false; - } + if !path_name.contains(&query_name) { + return false; } } } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 572a973..0000000 --- a/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -use failure::Fail; - -#[derive(Debug, Fail)] -pub enum AppError { - #[fail(display = "found invalid UTF-8 code sequence")] - UnicodeError, - - #[fail(display = "system clock is set to invalid time")] - SystemTimeError, - - #[fail(display = "unable to open database file")] - FileOpenError, - #[fail(display = "unable to lock database file")] - FileLockError, - - #[fail(display = "could not read from database")] - DBReadError, - #[fail(display = "could not write to database")] - DBWriteError, - - #[fail(display = "could not launch fzf")] - FzfLaunchError, - #[fail(display = "could not communicate with fzf")] - FzfIoError, - - #[fail(display = "could not retrieve home directory")] - GetHomeDirError, - #[fail(display = "could not retrieve current directory")] - GetCurrentDirError, - #[fail(display = "could not access path")] - PathAccessError, - - #[fail(display = "could not decode ${} in env", 0)] - EnvError(String), -} diff --git a/src/main.rs b/src/main.rs index 51319c6..1a08561 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,53 @@ -mod config; mod db; mod dir; -mod error; mod types; mod util; -use crate::config::get_zo_data; use crate::db::DB; -use crate::error::AppError; use crate::types::Timestamp; -use crate::util::{fzf_helper, get_current_time}; -use clap::{app_from_crate, crate_authors, crate_description, crate_name, crate_version}; -use clap::{App, Arg, SubCommand}; -use failure::ResultExt; +use crate::util::{fzf_helper, get_current_time, get_db}; +use anyhow::{anyhow, Context, Result}; +use clap::arg_enum; use std::env; use std::path::Path; +use structopt::StructOpt; + +// TODO: use structopt to parse env variables: + +arg_enum! { + #[allow(non_camel_case_types)] + #[derive(Debug)] + enum Shell { + bash, + fish, + zsh, + } +} + +#[derive(Debug, StructOpt)] +#[structopt(about = "A cd command that learns your habits")] +enum Zoxide { + #[structopt(about = "Add a new directory or increment its rank")] + Add { path: Option }, + + #[structopt(about = "Prints shell configuration")] + Init { + #[structopt(possible_values = &Shell::variants(), case_insensitive = true)] + shell: Shell, + #[structopt(long, help = "Prevents zoxide from defining any aliases other than 'z'")] + no_define_aliases: bool, + }, + + #[structopt(about = "Search for a directory")] + Query { + keywords: Vec, + #[structopt(short, long, help = "Opens an interactive selection menu using fzf")] + interactive: bool, + }, + + #[structopt(about = "Remove a directory")] + Remove { path: String }, +} fn zoxide_query(db: &mut DB, mut keywords: Vec, now: Timestamp) -> Option { if let [path] = keywords.as_slice() { @@ -36,105 +69,182 @@ fn zoxide_query(db: &mut DB, mut keywords: Vec, now: Timestamp) -> Optio fn zoxide_query_interactive( db: &mut DB, - mut keywords: Vec, + keywords: Vec, now: Timestamp, -) -> Result, failure::Error> { - db.remove_invalid(); - - for keyword in &mut keywords { - keyword.make_ascii_lowercase(); - } - - let dirs = db - .dirs - .iter() - .filter(|dir| dir.is_match(&keywords)) - .cloned() - .collect(); - +) -> Result> { + let dirs = db.query_all(keywords); fzf_helper(now, dirs) } -fn zoxide_app() -> App<'static, 'static> { - app_from_crate!() - .subcommand( - SubCommand::with_name("add") - .about("Add a new directory or increment its rank") - .author(crate_authors!()) - .version(crate_version!()) - .arg(Arg::with_name("PATH")), - ) - .subcommand( - SubCommand::with_name("query") - .about("Search for a directory") - .author(crate_authors!()) - .version(crate_version!()) - .arg( - Arg::with_name("interactive") - .short("i") - .long("interactive") - .takes_value(false) - .help("Opens an interactive selection menu using fzf"), - ) - .arg(Arg::with_name("KEYWORD").min_values(0)), - ) - .subcommand( - SubCommand::with_name("remove") - .about("Remove a directory") - .author(crate_authors!()) - .version(crate_version!()) - .arg(Arg::with_name("PATH").required(true)), - ) -} +pub fn main() -> Result<()> { + let opt = Zoxide::from_args(); + match opt { + Zoxide::Add { path: path_opt } => { + let mut db = get_db()?; + let now = get_current_time()?; -fn zoxide() -> Result<(), failure::Error> { - let matches = zoxide_app().get_matches(); + match path_opt { + Some(path) => db.add(path, now), + None => { + let current_dir = env::current_dir() + .with_context(|| anyhow!("unable to fetch current directory"))?; + db.add(current_dir, now) + } + }?; - let db_path = get_zo_data()?; - let mut db = DB::open(&db_path)?; - - if let Some(matches) = matches.subcommand_matches("query") { - let now = get_current_time()?; - - let keywords = matches - .values_of_os("KEYWORD") - .unwrap_or_default() - .map(|keyword| match keyword.to_str() { - Some(keyword) => Ok(keyword.to_owned()), - None => Err(AppError::UnicodeError), - }) - .collect::, _>>()?; - - let path_opt = if matches.is_present("interactive") { - zoxide_query_interactive(&mut db, keywords, now) - } else { - Ok(zoxide_query(&mut db, keywords, now)) - }?; - - if let Some(path) = path_opt { - println!("query: {}", path.trim()); + db.save()?; } - } else if let Some(matches) = matches.subcommand_matches("add") { - let now = get_current_time()?; - match matches.value_of_os("PATH") { - Some(path) => db.add(path, now)?, - None => { - let path = env::current_dir().with_context(|_| AppError::GetCurrentDirError)?; - db.add(path, now)?; + Zoxide::Init { + shell, + no_define_aliases, + } => { + match shell { + Shell::bash => { + println!("{}", INIT_BASH); + if !no_define_aliases { + println!("{}", INIT_BASH_ALIAS); + } + } + Shell::fish => { + println!("{}", INIT_FISH); + if !no_define_aliases { + println!("{}", INIT_FISH_ALIAS); + } + } + Shell::zsh => { + println!("{}", INIT_ZSH); + if !no_define_aliases { + println!("{}", INIT_ZSH_ALIAS); + } + } + }; + } + Zoxide::Query { + keywords, + interactive, + } => { + let mut db = get_db()?; + let now = get_current_time()?; + + let path_opt = if interactive { + zoxide_query_interactive(&mut db, keywords, now)? + } else { + zoxide_query(&mut db, keywords, now) + }; + + if let Some(path) = path_opt { + println!("query: {}", path.trim()); } - }; - } else if let Some(matches) = matches.subcommand_matches("remove") { - // unwrap is safe here because PATH has been set as a required field - let path = matches.value_of_os("PATH").unwrap(); - db.remove(path)?; - } + } + Zoxide::Remove { path } => { + let mut db = get_db()?; + db.remove(path)?; + db.save()?; + } + }; - db.save(db_path) + Ok(()) } -fn main() { - if let Err(err) = zoxide() { - eprintln!("zoxide: {}", err); - std::process::exit(1); - } +const INIT_BASH: &str = r#" +_zoxide_precmd() { + zoxide add } + +case "$PROMPT_COMMAND" in + *_zoxide_precmd*) ;; + *) PROMPT_COMMAND="_zoxide_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;; +esac + +z() { + if [ "$#" -eq 0 ]; then + cd "$HOME" + elif [ "$#" -eq 1 ] && [ "$1" = "-" ]; then + cd "-" + else + _Z_RESULT=$(zoxide query "$@") + case "$_Z_RESULT" in + "query: "*) + cd "${_Z_RESULT:7}" + ;; + *) + echo -n "$_Z_RESULT" + ;; + esac + fi +} +"#; + +const INIT_BASH_ALIAS: &str = r#" +alias zi="z -i" + +alias za="zoxide add" +alias zq="zoxide query" +alias zr="zoxide remove" +"#; + +const INIT_FISH: &str = r#" +function _zoxide_precmd --on-event fish_prompt + zoxide add +end + +function z + set -l argc (count $argv) + if test $argc -eq 0 + cd "$HOME" && commandline -f repaint + else if test $argc -eq 1 -a "$argv[1]" = "-" + cd "-" && commandline -f repaint + else + set _Z_RESULT (zoxide query $argv) + switch "$_Z_RESULT" + case 'query: *' + cd (string sub -s 8 -- "$_Z_RESULT") && commandline -f repaint + case '*' + echo -n "$_Z_RESULT" + end + end +end +"#; + +const INIT_FISH_ALIAS: &str = r#" +abbr -a zi "z -i" +abbr -a za "zoxide add" +abbr -a zq "zoxide query" +abbr -a zr "zoxide remove" +"#; + +const INIT_ZSH: &str = r#" +_zoxide_precmd() { + zoxide add +} + +[[ -n "${precmd_functions[(r)_zoxide_precmd]}" ]] || { + precmd_functions+=(_zoxide_precmd) +} + +z() { + if [ "$#" -eq 0 ]; then + cd "$HOME" + elif [ "$#" -eq 1 ] && [ "$1" = "-" ]; then + cd "-" + else + _Z_RESULT=$(zoxide query "$@") + case "$_Z_RESULT" in + "query: "*) + cd "${_Z_RESULT:7}" + ;; + *) + echo -n "$_Z_RESULT" + ;; + esac + fi +} +"#; + +const INIT_ZSH_ALIAS: &str = r#" +alias zi="z -i" + +alias za="zoxide add" +alias zq="zoxide query" +alias zr="zoxide remove" +"#; diff --git a/src/util.rs b/src/util.rs index bdaf19a..8769b4e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,29 +1,67 @@ +use crate::db::DB; use crate::dir::Dir; -use crate::error::AppError; -use crate::types::Timestamp; -use failure::ResultExt; +use crate::types::{Rank, Timestamp}; +use anyhow::{anyhow, Context, Result}; +use std::env; use std::io::{Read, Write}; +use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::SystemTime; -pub fn get_current_time() -> Result { +pub fn get_zo_data() -> Result { + const ZO_DATA: &str = "_ZO_DATA"; + + Ok(match env::var_os(ZO_DATA) { + Some(path) => PathBuf::from(path), + None => { + let mut path = + dirs::home_dir().ok_or_else(|| anyhow!("could not locate home directory"))?; + path.push(".zo"); + path + } + }) +} + +pub fn get_zo_maxage() -> Result { + const ZO_MAXAGE: &str = "_ZO_MAXAGE"; + + let maxage = match env::var_os(ZO_MAXAGE) { + Some(maxage_var) => maxage_var + .to_str() + .ok_or_else(|| anyhow!("invalid Unicode in ${}", ZO_MAXAGE))? + .parse::() + .with_context(|| anyhow!("could not parse ${} as integer", ZO_MAXAGE))? + as Rank, + None => 1000.0, + }; + Ok(maxage) +} + +pub fn get_db() -> Result { + let path = get_zo_data()?; + DB::open(path) +} + +pub fn get_current_time() -> Result { let current_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .with_context(|_| AppError::SystemTimeError)? + .with_context(|| "system clock set to invalid time")? .as_secs(); Ok(current_time as Timestamp) } -pub fn fzf_helper(now: Timestamp, mut dirs: Vec) -> Result, failure::Error> { +pub fn fzf_helper(now: Timestamp, mut dirs: Vec) -> Result> { let fzf = Command::new("fzf") .arg("-n2..") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() - .with_context(|_| AppError::FzfLaunchError)?; + .with_context(|| anyhow!("could not launch fzf"))?; - let mut fzf_stdin = fzf.stdin.ok_or_else(|| AppError::FzfIoError)?; + let mut fzf_stdin = fzf + .stdin + .ok_or_else(|| anyhow!("could not connect to fzf stdin"))?; for dir in dirs.iter_mut() { dir.rank = dir.get_frecency(now); @@ -42,15 +80,17 @@ pub fn fzf_helper(now: Timestamp, mut dirs: Vec) -> Result, }; writeln!(fzf_stdin, "{:>4} {}", frecency, dir.path) - .with_context(|_| AppError::FzfIoError)?; + .with_context(|| anyhow!("could not write into fzf stdin"))?; } - let mut fzf_stdout = fzf.stdout.ok_or_else(|| AppError::FzfIoError)?; + let mut fzf_stdout = fzf + .stdout + .ok_or_else(|| anyhow!("could not connect to fzf stdout"))?; let mut output = String::new(); fzf_stdout .read_to_string(&mut output) - .with_context(|_| AppError::FzfIoError)?; + .with_context(|| anyhow!("could not read from fzf stdout"))?; Ok(output.get(12..).map(str::to_owned)) } diff --git a/zoxide.plugin.zsh b/zoxide.plugin.zsh index ee1678c..2acda37 100644 --- a/zoxide.plugin.zsh +++ b/zoxide.plugin.zsh @@ -1,29 +1 @@ -#!/usr/bin/env sh - -_zoxide_precmd() { - zoxide add -} - -[[ -n "${precmd_functions[(r)_zoxide_precmd]}" ]] || { - precmd_functions+=(_zoxide_precmd) -} - -z() { - if [ $# -ne 0 ]; then - _Z_RESULT=$(zoxide query "$@") - case $_Z_RESULT in - "query: "*) - cd "${_Z_RESULT:7}" - ;; - *) - echo "${_Z_RESULT}" - ;; - esac - fi -} - -alias zi="z -i" - -alias za="zoxide add" -alias zq="zoxide query" -alias zr="zoxide remove" +eval "$(zoxide init zsh)"