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