ru_history/
lib.rs

1pub mod parse;
2pub mod sort;
3
4pub mod command_list;
5use std::collections::BTreeMap;
6use std::io::Write;
7use std::ops::Bound;
8use str_tools::traits::*;
9
10#[derive(Debug, PartialEq, Clone)]
11pub struct HistoryStore {
12    mp: BTreeMap<String, CommandData>,
13}
14
15#[derive(Debug, PartialEq, Clone)]
16pub struct CommandData {
17    paths: BTreeMap<String, HistoryItem>,
18    changed: bool,
19    recent: u64,
20    hits: usize,
21}
22
23#[derive(Debug, PartialEq, Clone)]
24pub struct HistoryItem {
25    recent: u64,
26    hits: usize,
27    r_hits: usize,
28}
29
30pub fn now() -> u64 {
31    use std::time::{Duration, SystemTime, UNIX_EPOCH};
32    SystemTime::now()
33        .duration_since(UNIX_EPOCH)
34        .unwrap_or(Duration::from_secs(0))
35        .as_secs()
36}
37
38pub fn here() -> String {
39    match std::env::current_dir() {
40        Ok(p) => p.display().to_string(),
41        Err(_) => String::new(),
42    }
43}
44
45impl HistoryStore {
46    pub fn new() -> Self {
47        Self {
48            mp: BTreeMap::new(),
49        }
50    }
51    pub fn add_cmd(&mut self, cmd: &str, dir: &str, time: u64) {
52        match self.mp.get_mut(cmd) {
53            Some(cd) => {
54                if time > cd.recent {
55                    cd.recent = time;
56                }
57                cd.add_dir(dir, time);
58                cd.hits += 1;
59            }
60            None => {
61                let mut cd = CommandData::new();
62                cd.hits += 1;
63                cd.recent = time;
64                cd.add_dir(dir, time);
65                self.mp.insert(cmd.to_string(), cd);
66            }
67        }
68    }
69
70    pub fn write_to<W: Write>(&mut self, w: &mut W, clean: bool) -> std::io::Result<()> {
71        for (cmd, dat) in &mut self.mp {
72            dat.write_to(w, cmd, clean)?;
73        }
74        Ok(())
75    }
76
77    pub fn complete<'a>(&'a self, pcmd: &str, dir: &str, n: usize) -> Vec<String> {
78        if pcmd == "" {
79            return command_list::top_n_commands((&self.mp).into_iter(), dir, n);
80        }
81        //Calculate last valid entry
82        let mut c_end = pcmd.to_string();
83        let cnext = c_end
84            .del_char()
85            .and_then(|c| std::char::from_u32((c as u32) + 1))
86            .unwrap_or('z');
87        c_end.push(cnext);
88        command_list::top_n_commands(
89            self.mp
90                .range::<str, _>((Bound::Included(pcmd), Bound::Excluded(c_end.as_str()))),
91            dir,
92            n,
93        )
94    }
95}
96
97impl CommandData {
98    fn new() -> Self {
99        CommandData {
100            paths: BTreeMap::new(),
101            changed: false,
102            recent: 0,
103            hits: 0,
104        }
105    }
106
107    fn write_to<W: Write>(&mut self, w: &mut W, cmd: &str, clean: bool) -> std::io::Result<()> {
108        if !clean && !self.changed {
109            return Ok(());
110        }
111        write!(w, "c{}\n", quoted(cmd))?;
112        for (k, v) in &mut self.paths {
113            v.write_to(w, k, clean)?;
114        }
115        self.changed = false;
116        Ok(())
117    }
118
119    fn add_dir(&mut self, dir: &str, time: u64) {
120        self.changed = true;
121        match self.paths.get_mut(dir) {
122            Some(it) => {
123                it.recent = time.max(it.recent);
124                it.r_hits += 1;
125            }
126            None => {
127                self.paths.insert(
128                    dir.to_string(),
129                    HistoryItem {
130                        r_hits: 1,
131                        recent: time,
132                        hits: 0,
133                    },
134                );
135            }
136        }
137    }
138}
139
140impl HistoryItem {
141    fn write_to<W: std::io::Write>(
142        &mut self,
143        w: &mut W,
144        path: &str,
145        clean: bool,
146    ) -> std::io::Result<()> {
147        if !clean && self.r_hits == 0 {
148            return Ok(());
149        }
150        let res = write!(w, "r{},h{},p{}\n", self.recent, self.r_hits, quoted(path));
151        self.hits += self.r_hits;
152        self.r_hits = 0;
153        res
154    }
155}
156
157fn quoted(s: &str) -> String {
158    let mut res = "\"".to_string();
159    for c in s.chars() {
160        match c {
161            '\"' => res.push_str("\\\""),
162            '\n' => res.push_str("\\n"),
163            '\\' => res.push_str("\\\\"),
164            _ => res.push(c),
165        }
166    }
167    res.push('\"');
168    res
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn multi_test() {
177        let mut h_store = HistoryStore::new();
178        h_store.add_cmd("do_1", "/home", 10);
179        h_store.add_cmd("do_2", "/park", 10);
180        h_store.add_cmd("do_1", "/car", 10);
181        h_store.add_cmd("do_1", "/home", 10);
182        h_store.add_cmd("stop_3", "/home", 11);
183        h_store.add_cmd("hello", "/home", 13);
184        h_store.add_cmd("help", "/home", 14);
185
186        let mut v: Vec<u8> = Vec::new();
187
188        h_store.write_to(&mut v, false).expect("Writing error");
189
190        h_store.add_cmd("do_1", "/home", 16);
191        h_store.add_cmd("hero", "/home", 17);
192
193        h_store.write_to(&mut v, false).expect("Writing error");
194
195        let s = String::from_utf8(v).unwrap();
196
197        let mut h_load = HistoryStore::new();
198        parse::parse_onto(&mut h_load, &s).expect("Parse OK");
199
200        assert_eq!(h_load, h_store);
201
202        let complete = h_store.complete("he", "/home", 2);
203        assert_eq!(complete, ["hero", "help"]);
204    }
205}