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 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}