blob: 2ee9152c014d3816ec6dd3f3c8c51d5061312037 [file] [log] [blame]
//! History API
use std::collections::VecDeque;
use std::path::Path;
use std::fs::File;
use super::Result;
pub struct History {
entries: VecDeque<String>,
max_len: usize,
}
const DEFAULT_HISTORY_MAX_LEN: usize = 100;
impl History {
pub fn new() -> History {
History { entries: VecDeque::new(), max_len: DEFAULT_HISTORY_MAX_LEN }
}
/// Return the history entry at position `index`, starting from 0.
pub fn get(& self, index: usize) -> Option<&String> {
return self.entries.get(index)
}
/// Add a new entry in the history.
pub fn add(&mut self, line: &str) -> bool {
if self.max_len == 0 {
return false;
}
if line.len() == 0 || line.chars().next().map_or(true, |c| c.is_whitespace()) { // ignorespace
return false;
}
let s = String::from(line); // TODO try to allocate only on push_back
if self.entries.back() == Some(&s) { // ignoredups
return false;
}
if self.entries.len() == self.max_len {
self.entries.pop_front();
}
self.entries.push_back(s);
return true;
}
pub fn len(&self) -> usize {
self.entries.len()
}
/// Set the maximum length for the history. This function can be called even
/// if there is already some history, the function will make sure to retain
/// just the latest `len` elements if the new history length value is smaller
/// than the amount of items already inside the history.
pub fn set_max_len(&mut self, len: usize) {
self.max_len = len;
if len == 0 {
self.entries.clear();
return;
}
loop {
if self.entries.len() <= len {
break;
}
self.entries.pop_front();
}
}
/// Save the history in the specified file.
pub fn save<P: AsRef<Path>+?Sized>(&self, path: &P) -> Result<()> {
use std::io::{BufWriter, Write};
if self.entries.len() == 0 {
return Ok(());
}
let file = try!(File::create(path));
let mut wtr = BufWriter::new(file);
for entry in self.entries.iter() {
try!(wtr.write_all(&entry.as_bytes()));
try!(wtr.write_all(b"\n"));
}
return Ok(());
}
/// Load the history from the specified file.
pub fn load<P: AsRef<Path>+?Sized>(&mut self, path: &P) -> Result<()> {
use std::io::{BufRead, BufReader};
let file = try!(File::open(&path));
let rdr = BufReader::new(file);
for line in rdr.lines() {
self.add(try!(line).as_ref()); // TODO truncate to MAX_LINE
}
return Ok(());
}
/// Clear history
pub fn clear(&mut self) {
self.entries.clear()
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use std::path::Path;
fn init() -> super::History {
let mut history = super::History::new();
assert!(history.add("line1"));
assert!(history.add("line2"));
assert!(history.add("line3"));
return history;
}
#[test]
fn new() {
let history = super::History::new();
assert_eq!(super::DEFAULT_HISTORY_MAX_LEN, history.max_len);
assert_eq!(0, history.entries.len());
}
#[test]
fn add() {
let mut history = super::History::new();
assert!(history.add("line1"));
assert!(history.add("line2"));
assert!(! history.add("line2"));
assert!(! history.add(""));
assert!(! history.add(" line3"));
}
#[test]
fn set_max_len() {
let mut history = init();
history.set_max_len(1);
assert_eq!(1, history.entries.len());
assert_eq!(Some(&"line3".to_string()), history.entries.back());
}
#[test]
fn save() {
let mut history = init();
let td = tempdir::TempDir::new_in(&Path::new("."), "histo").unwrap();
let history_path = td.path().join(".history");
history.save(&history_path).unwrap();
history.load(&history_path).unwrap();
td.close().unwrap();
}
}