├── .gitignore ├── Cargo.toml ├── README.md ├── examples ├── example.rs └── fileman.rs ├── LICENSE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.rlib 2 | *~ 3 | target 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "readline" 3 | version = "0.2.0" 4 | authors = ["Sean Perry", "Mika Attila"] 5 | build = "build.rs" 6 | description = "Simple wrapper around libreadline or libedit" 7 | repository = "https://github.com/shaleh/rust-readline" 8 | 9 | [dependencies] 10 | libc = "*" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rust-readline 2 | ============= 3 | 4 | Simple wrapper around readline for the Rust language 5 | 6 | Exposes: 7 | - `add_history(line: &str)` 8 | - `history() -> Vec` 9 | - `history_expand(input: &str) -> Result, String>` 10 | - `history_is_stifled() -> bool` 11 | - `stifle_history(n: i32)` 12 | - `unstifle_history() -> i32` 13 | - `readline(prompt: &str) -> Option` 14 | 15 | A Gitter [channel](https://gitter.im/shaleh/rust-readline) is available. 16 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | extern crate readline; 2 | 3 | fn main() { 4 | loop { 5 | let input = match readline::readline("Next: ") { 6 | Some(input) => input, 7 | None => { 8 | println!(""); 9 | break; 10 | }, 11 | }; 12 | 13 | if input == "quit" { 14 | break; 15 | } 16 | // add words that start with 'a' to the history to demonstrate 17 | else if input[0 .. 1] == "a".to_string() { 18 | readline::add_history(input.as_ref()); 19 | } 20 | 21 | println!("Input: '{}'", input); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sean Perry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A Simple wrapper for libreadline or libedit 2 | //! 3 | //! Exports seven functions: 4 | //! 5 | //! - `add_history` 6 | //! - `history` 7 | //! - `history_expand` 8 | //! - `history_is_stifled` 9 | //! - `stifle_history` 10 | //! - `unstifle_history` 11 | //! - `readline` 12 | //! 13 | 14 | extern crate libc; 15 | 16 | use libc::c_char; 17 | 18 | use std::ffi::CString; 19 | use std::ffi::CStr; 20 | use std::ptr; 21 | use std::string::String; 22 | 23 | mod ext_readline { 24 | use libc::{c_char, c_void}; 25 | 26 | #[repr(C)] 27 | pub struct HIST_ENTRY { 28 | pub line: *const c_char, 29 | pub data: *mut c_void, 30 | } 31 | 32 | extern "C" { 33 | /* History Support */ 34 | pub fn add_history(line: *const c_char); 35 | pub fn next_history() -> *const HIST_ENTRY; 36 | pub fn previous_history() -> *const HIST_ENTRY; 37 | pub fn history_expand(input: *const c_char, expansion: *mut *mut c_char) -> i32; 38 | pub fn stifle_history(n: i32); 39 | pub fn unstifle_history() -> i32; 40 | pub fn history_is_stifled() -> i32; 41 | 42 | /* readline */ 43 | pub fn readline(p: *const c_char) -> *const c_char; 44 | } 45 | } 46 | 47 | /// Update the internal history of input lines 48 | /// 49 | /// Call this after a successful `readline()` call to add that line to the 50 | /// history. 51 | pub fn add_history(line: &str) { 52 | unsafe { 53 | ext_readline::add_history(CString::new(line).unwrap().as_ptr()); 54 | } 55 | } 56 | 57 | pub fn stifle_history(n: i32) { 58 | unsafe { 59 | ext_readline::stifle_history(n); 60 | } 61 | } 62 | 63 | pub fn unstifle_history() -> i32 { 64 | unsafe { 65 | ext_readline::unstifle_history() 66 | } 67 | } 68 | 69 | pub fn history_is_stifled() -> bool { 70 | unsafe { 71 | ext_readline::history_is_stifled() != 0 72 | } 73 | } 74 | 75 | pub fn history_expand(input: &str) -> Result, String> { 76 | unsafe { 77 | let mut expansion: *mut c_char = ptr::null_mut(); 78 | let result = ext_readline::history_expand(CString::new(input).unwrap().as_ptr(), 79 | (&mut expansion)); 80 | if result == 0 { 81 | return Ok(None); 82 | } 83 | 84 | let slice = CStr::from_ptr(expansion); 85 | let bytes = slice.to_bytes(); 86 | let output = String::from_utf8_lossy(bytes).into_owned().clone(); 87 | 88 | libc::free(expansion as *mut libc::c_void); 89 | 90 | if result < 0 || result == 2 { 91 | Err(output) 92 | } 93 | else { 94 | Ok(Some(output)) 95 | } 96 | } 97 | } 98 | 99 | /// Invoke the external `readline()`. 100 | /// 101 | /// Returns an `Option` representing whether a `String` was returned 102 | /// or NULL. `None` indicates the user has signal end of input. 103 | pub fn readline(prompt: &str) -> Option { 104 | let cprmt = CString::new(prompt).unwrap().as_ptr(); 105 | unsafe { 106 | let ret = ext_readline::readline(cprmt); 107 | if ret.is_null() { // user pressed Ctrl-D 108 | None 109 | } 110 | else { 111 | let slice = CStr::from_ptr(ret); 112 | let bytes = slice.to_bytes(); 113 | 114 | // the return from readline needs to be explicitly freed 115 | // so clone the input first 116 | let line = String::from_utf8_lossy(bytes).into_owned().clone(); 117 | 118 | libc::free(ret as *mut libc::c_void); 119 | 120 | Some(line) 121 | } 122 | } 123 | } 124 | 125 | pub fn history() -> Vec { 126 | unsafe { 127 | loop { 128 | let value = ext_readline::previous_history(); 129 | if value.is_null() { 130 | break; 131 | } 132 | } 133 | 134 | let mut result: Vec = Vec::new(); 135 | 136 | loop { 137 | let value = ext_readline::next_history(); 138 | if value.is_null() { 139 | break; 140 | } 141 | 142 | let slice = CStr::from_ptr((*value).line); 143 | let bytes = slice.to_bytes(); 144 | let output = String::from_utf8_lossy(bytes).into_owned().clone(); 145 | 146 | result.push(output); 147 | } 148 | 149 | result 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /examples/fileman.rs: -------------------------------------------------------------------------------- 1 | extern crate readline; 2 | 3 | use std::env; 4 | use std::fs; 5 | use std::io; 6 | use std::process::Command; 7 | 8 | type Func = fn(&[&str]) -> Result; 9 | 10 | struct COMMAND { 11 | name: &'static str, /* User printable name of the function. */ 12 | func: Func, /* Function to call to do the job. */ 13 | expected_args: u32, /* number of expected args */ 14 | doc: &'static str, /* Documentation for this function. */ 15 | } 16 | 17 | fn io_error_to_string(err: io::Error) -> String { 18 | format!("{}", err) 19 | } 20 | 21 | fn com_cd(args: &[&str]) -> Result { 22 | let path = args[0]; 23 | 24 | try!(env::set_current_dir(path).map_err(io_error_to_string)); 25 | Ok(true) 26 | } 27 | 28 | fn com_delete(args: &[&str]) -> Result { 29 | let path = args[0]; 30 | let metadata = try!(fs::metadata(path).map_err(io_error_to_string)); 31 | 32 | if metadata.is_dir() { 33 | try!(fs::remove_dir(path).map_err(io_error_to_string)); 34 | } 35 | else { 36 | try!(fs::remove_file(path).map_err(io_error_to_string)); 37 | } 38 | 39 | Ok(true) 40 | } 41 | 42 | fn com_help(args: &[&str]) -> Result { 43 | let item = try!(find_command(args[0]).ok_or("no matching command".to_string())); 44 | println!("{}", item.doc); 45 | Ok(true) 46 | } 47 | 48 | fn com_list(args: &[&str]) -> Result { 49 | let path = args[0]; 50 | 51 | let iter = try!(fs::read_dir(path).map_err(io_error_to_string)); 52 | 53 | for entry in iter { 54 | let entry = try!(entry.map_err(io_error_to_string)); 55 | println!("{}", entry.path().display()); 56 | } 57 | 58 | Ok(true) 59 | } 60 | 61 | fn com_pwd(_: &[&str]) -> Result { 62 | let cwd = try!(env::current_dir().map_err(io_error_to_string)); 63 | println!("{}", cwd.display()); 64 | Ok(true) 65 | } 66 | 67 | fn com_quit(_: &[&str]) -> Result { 68 | Ok(false) 69 | } 70 | 71 | fn com_rename(args: &[&str]) -> Result { 72 | let src = args[0]; 73 | let dest = args[1]; 74 | 75 | println!("Rename {} to {}", src, dest); 76 | 77 | try!(fs::rename(src, dest).map_err(io_error_to_string)); 78 | 79 | Ok(true) 80 | } 81 | 82 | fn com_stat(args: &[&str]) -> Result { 83 | let path = args[0]; 84 | 85 | let result = try!(fs::metadata(path).map_err(io_error_to_string)); 86 | 87 | println!("Is File: {}", result.is_file()); 88 | println!("Size: {}", result.len()); 89 | 90 | Ok(true) 91 | } 92 | 93 | fn com_view(args: &[&str]) -> Result { 94 | let path = args[0]; 95 | 96 | let status = Command::new("more") 97 | .arg(path) 98 | .status() 99 | .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) }); 100 | if ! status.success() { 101 | println!("Attempt to view {} failed", path); 102 | } 103 | 104 | Ok(true) 105 | } 106 | 107 | fn com_history(_: &[&str]) -> Result { 108 | let values: Vec = readline::history(); 109 | println!("{:?}", values); 110 | 111 | Ok(true) 112 | } 113 | 114 | static COMMANDS_TABLE: [COMMAND; 12] = [ 115 | COMMAND { name: "cd", func: com_cd, 116 | expected_args: 1, doc: "Change to directory DIR" }, 117 | COMMAND { name: "delete", func: com_delete, 118 | expected_args: 1, doc: "Delete FILE" }, 119 | COMMAND { name: "help", func: com_help, 120 | expected_args: 1, doc: "Display this text" }, 121 | COMMAND { name: "?", func: com_help, 122 | expected_args: 0, doc: "Synonym for `help'" }, 123 | COMMAND { name: "list", func: com_list, 124 | expected_args: 1, doc: "List files in DIR" }, 125 | COMMAND { name: "ls", func: com_list, 126 | expected_args: 1, doc: "Synonym for `list'" }, 127 | COMMAND { name: "pwd", func: com_pwd, 128 | expected_args: 0, doc: "Print the current working directory" }, 129 | COMMAND { name: "quit", func: com_quit, 130 | expected_args: 0, doc: "Quit using Fileman" }, 131 | COMMAND { name: "rename", func: com_rename, 132 | expected_args: 2, doc: "Rename FILE to NEWNAME" }, 133 | COMMAND { name: "stat", func: com_stat, 134 | expected_args: 1, doc: "Print out statistics on FILE" }, 135 | COMMAND { name: "view", func: com_view, 136 | expected_args: 1, doc: "View the contents f FILE" }, 137 | COMMAND { name: "history", func: com_history, 138 | expected_args: 0, doc: "List editline history" }, 139 | ]; 140 | 141 | fn main () { 142 | 143 | readline::stifle_history(10); 144 | if ! readline::history_is_stifled() { 145 | panic!("Failed to stifle history"); 146 | } 147 | 148 | loop { 149 | let input = match readline::readline ("FileMan: ") { 150 | Some(line) => line.trim().to_string(), 151 | None => { 152 | break; 153 | }, 154 | }; 155 | 156 | if input.is_empty() { 157 | continue; 158 | } 159 | 160 | let command = match readline::history_expand(input.as_ref()) { 161 | // no expansion, just use the input 162 | Ok(None) => input.to_string(), 163 | // expansion found, use it 164 | Ok(Some(expansion)) => expansion, 165 | Err(_) => { 166 | continue; 167 | }, 168 | }; 169 | 170 | readline::add_history(command.as_ref()); 171 | 172 | match execute_line(&command) { 173 | Ok(keep_going) => { 174 | if ! keep_going { 175 | break; 176 | } 177 | }, 178 | Err(e) => { 179 | println!("Failed to execute: {}", e); 180 | }, 181 | } 182 | } 183 | } 184 | 185 | fn execute_line (line: &String) -> Result { 186 | let pieces: Vec<_> = line.split(" ").collect(); 187 | let word = pieces[0]; 188 | 189 | match find_command (word) { 190 | Some(command) => { 191 | let args = &pieces[1..]; 192 | if args.len() as u32 != command.expected_args { 193 | println!("Error: expected {} given {}", command.expected_args, args.len()); 194 | return Ok(true); 195 | } 196 | ((*command).func)(args).or_else(|e| { 197 | println!("Error: {}", e); 198 | Ok(true) // ignore the error and return Ok 199 | }) 200 | }, 201 | None => { 202 | return Err(format!("Failed to find: {}", word)); 203 | }, 204 | } 205 | } 206 | 207 | fn find_command (name: &str) -> Option<&COMMAND> { 208 | for i in 0..COMMANDS_TABLE.len() { 209 | if COMMANDS_TABLE[i].name == name { 210 | return Some(&COMMANDS_TABLE[i]); 211 | } 212 | } 213 | 214 | None 215 | } 216 | --------------------------------------------------------------------------------