pyc_shell/shell/
history.rs

1//! ## History
2//!
3//! `History` provides an API for the shell History
4
5/*
6*
7*   Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
8*
9* 	This file is part of "Pyc"
10*
11*   Pyc is free software: you can redistribute it and/or modify
12*   it under the terms of the GNU General Public License as published by
13*   the Free Software Foundation, either version 3 of the License, or
14*   (at your option) any later version.
15*
16*   Pyc is distributed in the hope that it will be useful,
17*   but WITHOUT ANY WARRANTY; without even the implied warranty of
18*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19*   GNU General Public License for more details.
20*
21*   You should have received a copy of the GNU General Public License
22*   along with Pyc.  If not, see <http://www.gnu.org/licenses/>.
23*
24*/
25
26use std::collections::VecDeque;
27
28pub struct ShellHistory {
29    history: VecDeque<String>
30}
31
32impl ShellHistory {
33
34    /// ### new
35    /// 
36    /// Instantiate a new ShellHistory
37    pub fn new() -> ShellHistory {
38        ShellHistory {
39            history: VecDeque::with_capacity(2048)
40        }
41    }
42
43    /// ### at
44    /// 
45    /// Get the command at a certain index of the history
46    /// None is returned in case index is out of range
47    pub fn at(&self, index: usize) -> Option<String> {
48        match self.history.get(index) {
49            Some(s) => Some(s.clone()),
50            None => None
51        }
52    }
53
54    /// ### clear
55    /// 
56    /// Clear history
57    pub fn clear(&mut self) {
58        self.history.clear();
59    }
60
61    /// ### dump
62    /// 
63    /// Dump history
64    pub fn dump(&mut self) -> Vec<String> {
65        let mut history: Vec<String> = Vec::with_capacity(self.history.len());
66        for entry in self.history.iter().rev() {
67            history.push(entry.clone());
68        }
69        history
70    }
71
72    /// ### len
73    /// 
74    /// Returns history len
75    pub fn len(&self) -> usize {
76        self.history.len()
77    }
78
79    /// ### load
80    /// 
81    /// Load history
82    /// NOTE: the maximum history size will still be the size provided at constructor
83    pub fn load(&mut self, lines: Vec<String>) {
84        //Clear current history
85        self.clear();
86        //Parse file
87        for line in lines.iter() {
88            self.push(line.clone());
89        }
90    }
91
92    /// ### push
93    /// 
94    /// Push a new entry to the history.
95    /// The entry is stored at the front of the history. The first the newest
96    pub fn push(&mut self, mut line: String) {
97        //@! Remove newline
98        while line.ends_with("\n") {
99            line.pop();
100        }
101        //Ignore empty lines
102        if line.is_empty() {
103            return;
104        }
105        //Duplicates not allowed
106        if let Some(last_line) = self.at(0) {
107            if last_line == line {
108                return
109            }
110        }
111        //Check if history overflows the size
112        let size: usize = (self.history.capacity() + 1) / 2;
113        if self.history.len() + 1 > size {
114            self.history.pop_back();
115        }
116        self.history.push_front(line);
117    }
118
119}
120
121//@! Test module
122
123#[cfg(test)]
124mod tests {
125
126    use super::*;
127
128    #[test]
129    fn test_shell_history() {
130        let mut history: ShellHistory = ShellHistory::new();
131        assert_eq!(history.history.capacity(), (2048 * 2 - 1)); //2048 * 2 - 1
132        //Load history
133        history.load(vec![String::from("ls"), String::from("cd /tmp/")]);
134        assert_eq!(history.len(), 2);
135        //History at
136        assert_eq!(history.at(0).unwrap(), String::from("cd /tmp/"));
137        assert_eq!(history.at(1).unwrap(), String::from("ls"));
138        assert!(history.at(2).is_none());
139        //Push element
140        history.push(String::from("pwd\n\n\n")); //@! Newlines must be removed
141        assert_eq!(history.len(), 3);
142        assert_eq!(history.at(0).unwrap(), String::from("pwd"));
143        //Duplicates are not allowed
144        history.push(String::from("pwd"));
145        assert_eq!(history.len(), 3);
146        //Empty lines are not allowed
147        history.push(String::from("\n"));
148        assert_eq!(history.len(), 3);
149        //Fill history with 2048 elements
150        let mut history_vec: Vec<String> = Vec::with_capacity(2048);
151        for i in 0..2048 {
152            history_vec.push(format!("echo {}", i));
153        }
154        history.load(history_vec);
155        assert_eq!(history.len(), 2048);
156        assert_eq!(history.at(0).unwrap(), String::from("echo 2047"));
157        assert_eq!(history.at(2047).unwrap(), String::from("echo 0"));
158        //Push element
159        history.push(String::from("echo 2048"));
160        assert_eq!(history.len(), 2048);
161        assert_eq!(history.at(0).unwrap(), String::from("echo 2048"));
162        assert_eq!(history.at(2047).unwrap(), String::from("echo 1"));
163        //Clear
164        history.clear();
165        assert_eq!(history.len(), 0);
166        //Push element
167        history.push(String::from("ls -l"));
168        history.push(String::from("cd /tmp/"));
169        //Dump history
170        let dump: Vec<String> = history.dump();
171        assert_eq!(dump.len(), 2);
172        //Older commands first
173        assert_eq!(*dump.get(0).unwrap(), String::from("ls -l"));
174        assert_eq!(*dump.get(1).unwrap(), String::from("cd /tmp/"));
175    }
176
177}