1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//! z_rs implements a directory jumping command line utility //! //! The original is a 200 line shell classic //! https://github.com/rupa/z/blob/master/z.sh //! //! This is an **opinionated** rewrite instead of being 1:1 //! with the original. Most notably, this does not override //! `cd` in any way, and needs to be used instead of `cd` to //! build the cache. //! //! This provides a `z_rs` binary, which can be invoked //! for configuration and lookup. //! //! ## Installation //! //! Currently unpublished, do //! //! ```sh //! cargo install https://github.com/suyash/z-rs //! ``` //! //! ## Configuration //! //! add //! //! ```sh //! eval "$(z_rs init --cmd z --cache $HOME/.z_rs)" //! ``` //! //! in your shell configuration file. //! //! The `--cmd` option is the name of the command that will //! be added to the shell. For example, in this case, a `z` //! command will be created. //! //! ## Usage //! //! If configured with the command name `z`, then simply do //! //! ```sh //! z foo bar //! ``` //! //! will try to match `foo` and `bar` and if a match is found //! in the cache, will `cd` to it, otherwise error out. //! //! Matches occur based on "frecency". The rank of a path is //! how many times it has been accessed. The command will return //! the highest ranked matching record. If there are multiple, //! it will return the most recently accessed one. #![deny(missing_docs)] use std::collections::HashMap; use bstr::ByteSlice; use serde::{Deserialize, Serialize}; #[cfg(test)] mod tests; #[derive(Serialize, Deserialize, Debug)] /// Records contains a record of all stored values pub struct Records { map: HashMap<Vec<u8>, (f64, u64)>, sum: f64, } impl Records { /// new creates a new Records instance pub fn new() -> Records { Self::default() } /// update increments the rank of the provided path /// and updates its timestamp pub fn update<T>(&mut self, path: T, ts: u64) where T: AsRef<[u8]>, { let v = self.map.entry(path.as_ref().to_owned()).or_insert((0.0, 0)); v.0 += 1.0; v.1 = ts; self.sum += 1.0; // TODO: implement pruning and reorging } /// query matches a record from the saved records from the given /// arguments. The rank of a record is how many times it has been used. /// The match is returned based on "frecency", i.e. /// the highest ranked matching record is given priority, /// if there are multiple then the most recently accessed record /// with the same rank is returned. pub fn query<T>(&self, arguments: &[T]) -> Option<Vec<u8>> where T: AsRef<[u8]>, { let mut ans = None; // NOTE: since rust hash maps are swiss tables now, // assuming straight up iteration is a feasible op for (path, (score, timestamp)) in self.map.iter() { let mut index = 0; for argument in arguments { match path[index..].find(argument) { None => { index = path.len() + 1; break; } Some(fix) => { index += fix + argument.as_ref().len(); } } } if index <= path.len() { ans = match ans { None => Some((path, (score, timestamp))), Some((ppath, (pscore, ptimestamp))) => { if score > pscore || ((score - pscore).abs() < std::f64::EPSILON && timestamp > ptimestamp) { Some((path, (score, timestamp))) } else { Some((ppath, (pscore, ptimestamp))) } } }; } } match ans { None => None, Some((path, _)) => Some(path.clone()), } } /// query_and_update runs a query followed by an update /// with the result of the query. pub fn query_and_update<T>(&mut self, arguments: &[T], ts: u64) -> Option<Vec<u8>> where T: AsRef<[u8]>, { match self.query(arguments) { None => None, Some(v) => { self.update(&v, ts); Some(v) } } } } impl Default for Records { fn default() -> Self { Records { map: HashMap::new(), sum: 0.0, } } }