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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
extern crate libc;

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

#[derive(Debug)]
pub struct ReadlineError {
    desc: String,
    detail: String,
}

impl ReadlineError {
    pub fn new<T>(desc: &str, detail: T) -> ReadlineError where T: fmt::Debug {
        ReadlineError {
            desc: String::from(desc),
            detail: format!("{:?}", detail),
        }
    }
}

impl From<std::ffi::NulError> for ReadlineError {
    fn from(e: std::ffi::NulError) -> ReadlineError {
        ReadlineError::new("NulError", e)
    }
}

impl From<std::str::Utf8Error> for ReadlineError {
    fn from(e: std::str::Utf8Error) -> ReadlineError {
        ReadlineError::new("FromUtf8Error", e)
    }
}

impl From<std::io::Error> for ReadlineError {
    fn from(e: std::io::Error) -> ReadlineError {
        ReadlineError::new("I/O Error", e)
    }
}

impl fmt::Display for ReadlineError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}: {}", self.desc, self.detail)
    }
}

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) -> Result<(), ReadlineError> {
    unsafe {
        let cline = try!(CString::new(&(line.as_bytes())[..]));
        ext_readline::add_history(cline.as_ptr());
        Ok(())
    }
}

pub fn readline(prompt: String) -> Result<String, ReadlineError> {
    let cprmt = try!(CString::new(&(prompt.as_bytes())[..]));
    unsafe {
        let ret = ext_readline::readline(cprmt.as_ptr());
        if ret.is_null() {  // user pressed Ctrl-D
            Err(ReadlineError::new("Break", "null on readline, should break."))
        } else {
            let slice = CStr::from_ptr(ret);
            let res = try!(str::from_utf8(slice.to_bytes()));
            Ok(res.to_string())
        }
    }
}

pub fn preload_history(file: &Path) -> Result<(), ReadlineError> {
    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() {
            match opt {
                Ok(o) => try!(add_history(o)),
                Err(_) => {
                    return Err(ReadlineError::new(
                        "ReadlineError",
                        "Unable to preload history!"
                    ))
                },
            }
        }
    }

    Ok(())
}

pub fn add_history_persist(
    line: String,
    file: &Path
) -> Result<(), ReadlineError> {
    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);
        try!(oo.open(file))
    } else {
        try!(File::create(file))
    });

    // Only add the line to the history file if it doesn't already
    // contain the line to add.
    let read = BufReader::new(try!(File::open(file)));
    // The lines method returns strings without the trailing '\n'
    let mut cmds: Vec<String> = Vec::new();

    for line in read.lines() {
        match line {
            Ok(l) => cmds.push(l),
            Err(_) => return Err(ReadlineError {
                desc: String::from("ReadlineError"),
                detail: String::from("Unable to parse history file!"),
            }),
        }
    }

    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.
        try!(write.write(line.as_bytes()));
    }

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

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

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