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() {
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))
});
let read = BufReader::new(try!(File::open(file)));
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();
if !cmds.contains(&trimmed) && !trimmed.is_empty() {
try!(write.write(line.as_bytes()));
}
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());
}
}