1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
extern crate libc;

use std::ffi::{CStr,CString};
use std::io::prelude::*;
use std::fs::{self,File,OpenOptions};
use std::io::{BufReader,LineWriter};
use std::path::Path;
use std::str;

mod ext_readline {
    use libc::c_char;

    extern {
        pub fn add_history(line: *const c_char);
        pub fn readline(p: *const c_char) -> *const c_char;
    }
}

pub fn add_history(line: String) {
    unsafe {
        let cline = CString::new(&(line.as_bytes())[..]).unwrap();
        ext_readline::add_history(cline.as_ptr());
    }
}

pub fn readline(prompt: String) -> Option<String> {
    let cprmt = CString::new(&(prompt.as_bytes())[..]).unwrap();
    unsafe {
        let ret = ext_readline::readline(cprmt.as_ptr());
        if ret.is_null() {  // user pressed Ctrl-D
            None
        } else {
            let slice = CStr::from_ptr(ret);
            let res = str::from_utf8(slice.to_bytes())
                .ok().expect("Failed to parse utf-8");
            Some(res.to_string())
        }
    }
}

pub fn preload_history(file: &Path) {
    let exists = match fs::metadata(file) {
        Ok(meta) => meta.is_file(),
        Err(_)   => false,
    };

    if exists {
        let file = BufReader::new(File::open(file).unwrap());
        for opt in file.lines() {
            add_history(opt.unwrap());
        }
    }
}

pub fn add_history_persist(line: String, file: &Path) {
    let exists = match fs::metadata(file) {
        Ok(meta) => meta.is_file(),
        Err(_)   => false,
    };

    let mut write = LineWriter::new(if exists {
        let mut oo = OpenOptions::new();
        oo.append(true);
        oo.write(true);
        oo.open(file).unwrap()
    } else {
        File::create(file).unwrap()
    });

    // Only add the line to the history file if it doesn't already
    // contain the line to add.
    let read = BufReader::new(File::open(file).unwrap());
    // The lines method returns strings without the trailing '\n'
    let cmds: Vec<String> = read.lines().map(|l| l.unwrap()).collect();
    let trimmed = line.trim_right().to_string();

    // Only add the line to history if it doesn't exist already and isn't empty.
    if !cmds.contains(&trimmed) && !trimmed.is_empty() {
        // Write the line with the trailing '\n' to the file.
        let _ = write.write(line.as_bytes());
    }

    // Add the line witout the trailing '\n' to the readline history.
    add_history(trimmed);
}

#[cfg(test)]
mod test {
    use super::add_history;

    #[test]
    fn test_readline() {
        add_history("test".to_string());
    }
}