diff --git a/Cargo.toml b/Cargo.toml index c17e7d9ce4..47bdee85f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ -[package] -name = "shrust" -version = "0.0.4" -description = "A library for creating interactive command line shells in Rust" -authors = ["Pierre-Henri Symoneaux"] -repository = "https://github.com/phsym/shrust" -homepage = "https://github.com/phsym/shrust" -documentation = "http://phsym.github.io/shrust" -readme = "README.md" -license = "MIT" -keywords = ["shell", "console", "interactive", "terminal", "command"] - -[dependencies] -prettytable-rs = "^0.6" +[package] +name = "shrust" +version = "0.0.4" +description = "A library for creating interactive command line shells in Rust" +authors = ["Pierre-Henri Symoneaux"] +repository = "https://github.com/phsym/shrust" +homepage = "https://github.com/phsym/shrust" +documentation = "http://phsym.github.io/shrust" +readme = "README.md" +license = "MIT" +keywords = ["shell", "console", "interactive", "terminal", "command"] + +[dependencies] +prettytable-rs = "^0.6" diff --git a/examples/data.rs b/examples/data.rs index 07d8690845..74a49b965c 100644 --- a/examples/data.rs +++ b/examples/data.rs @@ -1,21 +1,21 @@ -extern crate shrust; -use shrust::{Shell, ShellIO}; -use std::io::prelude::*; - -fn main() { - let v = Vec::new(); - let mut shell = Shell::new(v); - shell.new_command("push", "Add string to the list", 1, |io, v, s| { - try!(writeln!(io, "Pushing {}", s[0])); - v.push(s[0].to_string()); - Ok(()) - }); - shell.new_command_noargs("list", "List strings", |io, v| { - for s in v { - try!(writeln!(io, "{}", s)); - } - Ok(()) - }); - - shell.run_loop(&mut ShellIO::default()); -} +extern crate shrust; +use shrust::{Shell, ShellIO}; +use std::io::prelude::*; + +fn main() { + let v = Vec::new(); + let mut shell = Shell::new(v); + shell.new_command("push", "Add string to the list", 1, |io, v, s| { + try!(writeln!(io, "Pushing {}", s[0])); + v.push(s[0].to_string()); + Ok(()) + }); + shell.new_command_noargs("list", "List strings", |io, v| { + for s in v { + try!(writeln!(io, "{}", s)); + } + Ok(()) + }); + + shell.run_loop(&mut ShellIO::default()); +} diff --git a/examples/default.rs b/examples/default.rs index 8e2978c1fb..70e256e897 100644 --- a/examples/default.rs +++ b/examples/default.rs @@ -1,12 +1,12 @@ -extern crate shrust; -use shrust::{Shell, ShellIO}; -use std::io::prelude::*; - -fn main() { - let mut shell = Shell::new(()); - shell.set_default(|io, _, cmd| { - try!(writeln!(io, "Hello from default handler !!! Received: {}", cmd)); - Ok(()) - }); - shell.run_loop(&mut ShellIO::default()); -} +extern crate shrust; +use shrust::{Shell, ShellIO}; +use std::io::prelude::*; + +fn main() { + let mut shell = Shell::new(()); + shell.set_default(|io, _, cmd| { + try!(writeln!(io, "Hello from default handler !!! Received: {}", cmd)); + Ok(()) + }); + shell.run_loop(&mut ShellIO::default()); +} diff --git a/examples/dummy.rs b/examples/dummy.rs index 655efc8b8e..8b80fc28b6 100644 --- a/examples/dummy.rs +++ b/examples/dummy.rs @@ -1,13 +1,13 @@ -extern crate shrust; -use shrust::{Shell, ShellIO}; -use std::io::prelude::*; - -fn main() { - let mut shell = Shell::new(()); - shell.new_command_noargs("hello", "Say 'hello' to the world", |io, _| { - try!(writeln!(io, "Hello World !!!")); - Ok(()) - }); - - shell.run_loop(&mut ShellIO::default()); -} +extern crate shrust; +use shrust::{Shell, ShellIO}; +use std::io::prelude::*; + +fn main() { + let mut shell = Shell::new(()); + shell.new_command_noargs("hello", "Say 'hello' to the world", |io, _| { + try!(writeln!(io, "Hello World !!!")); + Ok(()) + }); + + shell.run_loop(&mut ShellIO::default()); +} diff --git a/examples/map.rs b/examples/map.rs index ebd3e6dc5e..52dad14119 100644 --- a/examples/map.rs +++ b/examples/map.rs @@ -1,39 +1,39 @@ -extern crate shrust; -use shrust::{Shell, ShellIO}; -use std::io::prelude::*; - -use std::collections::HashMap; -use std::str::FromStr; - -fn main() { - let map = HashMap::new(); - let mut shell = Shell::new(map); - - shell.new_command("put", "Insert a value", 2, |_, map, args| { - map.insert(try!(usize::from_str(args[0])), args[1].to_string()); - Ok(()) - }); - shell.new_command("get", "Get a value", 1, |mut io, map, args| { - match map.get(&try!(usize::from_str(args[0]))) { - Some(val) => writeln!(io, "{}", val).unwrap(), - None => writeln!(io, "Not found").unwrap() - }; - Ok(()) - }); - shell.new_command("remove", "Remove a value", 1, |_, map, args| { - map.remove(&try!(usize::from_str(args[0]))); - Ok(()) - }); - shell.new_command("list", "List all values", 0, |mut io, map, _| { - for (k, v) in map { - writeln!(io, "{} = {}", k, v).unwrap(); - } - Ok(()) - }); - shell.new_command_noargs("clear", "Clear all values", |_, map| { - map.clear(); - Ok(()) - }); - - shell.run_loop(&mut ShellIO::default()); -} +extern crate shrust; +use shrust::{Shell, ShellIO}; +use std::io::prelude::*; + +use std::collections::HashMap; +use std::str::FromStr; + +fn main() { + let map = HashMap::new(); + let mut shell = Shell::new(map); + + shell.new_command("put", "Insert a value", 2, |_, map, args| { + map.insert(try!(usize::from_str(args[0])), args[1].to_string()); + Ok(()) + }); + shell.new_command("get", "Get a value", 1, |mut io, map, args| { + match map.get(&try!(usize::from_str(args[0]))) { + Some(val) => writeln!(io, "{}", val).unwrap(), + None => writeln!(io, "Not found").unwrap() + }; + Ok(()) + }); + shell.new_command("remove", "Remove a value", 1, |_, map, args| { + map.remove(&try!(usize::from_str(args[0]))); + Ok(()) + }); + shell.new_command("list", "List all values", 0, |mut io, map, _| { + for (k, v) in map { + writeln!(io, "{} = {}", k, v).unwrap(); + } + Ok(()) + }); + shell.new_command_noargs("clear", "Clear all values", |_, map| { + map.clear(); + Ok(()) + }); + + shell.run_loop(&mut ShellIO::default()); +} diff --git a/examples/socket.rs b/examples/socket.rs index 88961e4002..782e2cf10e 100644 --- a/examples/socket.rs +++ b/examples/socket.rs @@ -1,51 +1,51 @@ -extern crate shrust; -use shrust::{Shell, ShellIO}; -use std::io::prelude::*; - -use std::sync::{Arc, Mutex}; -use std::thread; - -use std::collections::HashMap; -use std::str::FromStr; - -use std::net::TcpListener; - -fn main() { - let map = Arc::new(Mutex::new(HashMap::new())); - - let mut shell = Shell::new(map); - - shell.new_command("put", "Insert a value", 2, |_, map, args| { - map.lock().unwrap().insert(try!(usize::from_str(args[0])), args[1].to_string()); - Ok(()) - }); - shell.new_command("get", "Get a value", 1, |mut io, map, args| { - match map.lock().unwrap().get(&try!(usize::from_str(args[0]))) { - Some(val) => writeln!(io, "{}", val).unwrap(), - None => writeln!(io, "Not found").unwrap() - }; - Ok(()) - }); - shell.new_command("remove", "Remove a value", 1, |_, map, args| { - map.lock().unwrap().remove(&try!(usize::from_str(args[0]))); - Ok(()) - }); - shell.new_command("list", "List all values", 0, |mut io, map, _| { - for (k, v) in &*map.lock().unwrap() { - writeln!(io, "{} = {}", k, v).unwrap(); - } - Ok(()) - }); - shell.new_command_noargs("clear", "Clear all values", |_, map| { - map.lock().unwrap().clear(); - Ok(()) - }); - - let serv = TcpListener::bind("0.0.0.0:1234").expect("Cannot open socket"); - for sock in serv.incoming() { - let sock = sock.unwrap(); - let mut shell = shell.clone(); - let mut io = ShellIO::new_io(sock); - thread::spawn(move || shell.run_loop(&mut io)); - } -} +extern crate shrust; +use shrust::{Shell, ShellIO}; +use std::io::prelude::*; + +use std::sync::{Arc, Mutex}; +use std::thread; + +use std::collections::HashMap; +use std::str::FromStr; + +use std::net::TcpListener; + +fn main() { + let map = Arc::new(Mutex::new(HashMap::new())); + + let mut shell = Shell::new(map); + + shell.new_command("put", "Insert a value", 2, |_, map, args| { + map.lock().unwrap().insert(try!(usize::from_str(args[0])), args[1].to_string()); + Ok(()) + }); + shell.new_command("get", "Get a value", 1, |mut io, map, args| { + match map.lock().unwrap().get(&try!(usize::from_str(args[0]))) { + Some(val) => writeln!(io, "{}", val).unwrap(), + None => writeln!(io, "Not found").unwrap() + }; + Ok(()) + }); + shell.new_command("remove", "Remove a value", 1, |_, map, args| { + map.lock().unwrap().remove(&try!(usize::from_str(args[0]))); + Ok(()) + }); + shell.new_command("list", "List all values", 0, |mut io, map, _| { + for (k, v) in &*map.lock().unwrap() { + writeln!(io, "{} = {}", k, v).unwrap(); + } + Ok(()) + }); + shell.new_command_noargs("clear", "Clear all values", |_, map| { + map.lock().unwrap().clear(); + Ok(()) + }); + + let serv = TcpListener::bind("0.0.0.0:1234").expect("Cannot open socket"); + for sock in serv.incoming() { + let sock = sock.unwrap(); + let mut shell = shell.clone(); + let mut io = ShellIO::new_io(sock); + thread::spawn(move || shell.run_loop(&mut io)); + } +} diff --git a/src/lib.rs b/src/lib.rs index 736a0487a0..4f10165544 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,382 +1,382 @@ -//! A library for creating interactive command line shells -#[macro_use] extern crate prettytable; -use prettytable::Table; -use prettytable::format; - -use std::io; -use std::io::prelude::*; -use std::string::ToString; -use std::error::Error; -use std::fmt; -use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, Mutex}; - -use std::collections::BTreeMap; - -/// Command execution error -#[derive(Debug)] -pub enum ExecError { - /// Empty command provided - Empty, - /// Exit from the shell loop - Quit, - /// Some arguments are missing - MissingArgs, - /// The provided command is unknown - UnknownCommand(String), - /// The history index is not valid - InvalidHistory(usize), - /// Other error that may have happen during command execution - Other(Box), -} -use ExecError::*; - -impl fmt::Display for ExecError { - fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result { - return match self { - &Empty => write!(format, "No command provided"), - &Quit => write!(format, "Quit"), - &UnknownCommand(ref cmd) => write!(format, "Unknown Command {}", cmd), - &InvalidHistory(i) => write!(format, "Invalid history entry {}", i), - &MissingArgs => write!(format, "Not enough arguments"), - &Other(ref e) => write!(format, "{}", e) - }; - } -} - -// impl Error for ExecError { -// fn description(&self) -> &str { -// return match self { -// &Quit => "The command requested to quit", -// &UnknownCommand(..) => "The provided command is unknown", -// &MissingArgs => "Not enough arguments have been provided", -// &Other(..) => "Other error occured" -// }; -// } -// } - -impl From for ExecError { - fn from(e: E) -> ExecError { - return Other(Box::new(e)); - } -} - -/// Input / Output for shell execution -#[derive(Clone)] -pub struct ShellIO { - input: Arc>, - output: Arc> -} - -impl ShellIO { - /// Create a new Shell I/O wrapping provided Input and Output - pub fn new(input: I, output: O) -> ShellIO - where I: Read + Send + 'static, O: Write + Send + 'static - { - return ShellIO { - input: Arc::new(Mutex::new(input)), - output: Arc::new(Mutex::new(output)) - }; - } - - /// Create a new Shell I/O wrapping provided Read/Write io - pub fn new_io(io: T) -> ShellIO - where T: Read + Write + Send + 'static - { - let io = Arc::new(Mutex::new(io)); - return ShellIO { - input: io.clone(), - output: io - }; - } -} - -impl Read for ShellIO { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - return self.input.lock().expect("Cannot get handle to console input").read(buf); - } -} - -impl Write for ShellIO { - fn write(&mut self, buf: &[u8]) -> io::Result { - return self.output.lock().expect("Cannot get handle to console output").write(buf); - } - - fn flush(&mut self) -> io::Result<()> { - return self.output.lock().expect("Cannot get handle to console output").flush(); - } -} - -impl Default for ShellIO { - fn default() -> Self { - return Self::new(io::stdin(), io::stdout()); - } -} - - -/// Result from command execution -pub type ExecResult = Result<(), ExecError>; - -/// A shell -pub struct Shell { - commands: BTreeMap>>, - default: Arc, &str) -> ExecResult + Send + Sync>, - data: T, - prompt: String, - unclosed_prompt: String, - history: History -} - -impl Shell { - /// Create a new shell, wrapping `data`, using provided IO - pub fn new(data: T) -> Shell { - let mut sh = Shell { - commands: BTreeMap::new(), - default: Arc::new(|_, _, cmd| Err(UnknownCommand(cmd.to_string()))), - data: data, - prompt: String::from(">"), - unclosed_prompt: String::from(">"), - history: History::new(10), - }; - sh.register_command(builtins::help_cmd()); - sh.register_command(builtins::quit_cmd()); - sh.register_command(builtins::history_cmd()); - return sh; - } - - /// Get a mutable pointer to the inner data - pub fn data(&mut self) -> &mut T { - return &mut self.data; - } - - /// Change the current prompt - pub fn set_prompt(&mut self, prompt: String) { - self.prompt = prompt; - } - - /// Change the current unclosed prompt - pub fn set_unclosed_prompt(&mut self, prompt: String) { - self.unclosed_prompt = prompt; - } - - fn register_command(&mut self, cmd: builtins::Command) { - self.commands.insert(cmd.name.clone(), Arc::new(cmd)); - } - - // Set a custom default handler, invoked when a command is not found - pub fn set_default(&mut self, func: F) - where F: Fn(&mut ShellIO, &mut Shell, &str) -> ExecResult + Send + Sync + 'static - { - self.default = Arc::new(func); - } - - /// Register a shell command. - /// Shell commands get called with a reference to the current shell - pub fn new_shell_command(&mut self, name: S, description: S, nargs: usize, func: F) - where S: ToString, F: Fn(&mut ShellIO, &mut Shell, &[&str]) -> ExecResult + Send + Sync + 'static - { - self.register_command(builtins::Command::new(name.to_string(), description.to_string(), nargs, Box::new(func))); - } - - /// Register a command - pub fn new_command(&mut self, name: S, description: S, nargs: usize, func: F) - where S: ToString, F: Fn(&mut ShellIO, &mut T, &[&str]) -> ExecResult + Send + Sync + 'static - { - self.new_shell_command(name, description, nargs, move |io, sh, args| func(io, sh.data(), args)); - } - - /// Register a command that do not accept any argument - pub fn new_command_noargs(&mut self, name: S, description: S, func: F) - where S: ToString, F: Fn(&mut ShellIO, &mut T) -> ExecResult + Send + Sync + 'static - { - self.new_shell_command(name, description, 0, move |io, sh, _| func(io, sh.data())); - } - - /// Print the help to stdout - pub fn print_help(&self, io: &mut ShellIO) -> ExecResult { - let mut table = Table::new(); - table.set_format(*format::consts::FORMAT_CLEAN); - for cmd in self.commands.values() { - table.add_row(cmd.help()); - } - return table.print(io).map_err(|e| From::from(e)) - } - - /// Return the command history - pub fn get_history(&self) -> &History { - return &self.history; - } - - /// Evaluate a command line - pub fn eval(&mut self, io: &mut ShellIO, line: &str) -> ExecResult { - let mut splt = line.trim().split_whitespace(); - return match splt.next() { - None => Err(Empty), - Some(cmd) => match self.commands.get(cmd).map(|i| i.clone()) { - None => self.default.clone()(io, self, line), - Some(c) => c.run(io, self, &splt.collect::>()) - } - }; - } - - fn print_prompt(&self, io: &mut ShellIO, unclosed: bool) { - if unclosed { - write!(io, "{} ", self.unclosed_prompt).unwrap(); - } else { - write!(io, "{} ", self.prompt).unwrap(); - } - io.flush().unwrap(); - } - - /// Enter the shell main loop, exiting only when - /// the "quit" command is called - pub fn run_loop(&mut self, io: &mut ShellIO) { - self.print_prompt(io, false); - let stdin = io::BufReader::new(io.clone()); - let mut iter = stdin.lines().map(|l| l.unwrap()); - while let Some(mut line) = iter.next() { - while line.len() > 0 && &line[line.len()-1 ..] == "\\" { - self.print_prompt(io, true); - line.pop(); - line.push_str(&iter.next().unwrap()) - } - if let Err(e) = self.eval(io, &line) { - match e { - Empty => {}, - Quit => return, - e @ _ => writeln!(io, "Error : {}", e).unwrap() - }; - } else { - self.get_history().push(line); - } - self.print_prompt(io, false); - } - } -} - -impl Deref for Shell { - type Target = T; - fn deref(&self) -> &T { - return &self.data; - } -} - -impl DerefMut for Shell { - fn deref_mut(&mut self) -> &mut T { - return &mut self.data; - } -} - -impl Clone for Shell where T: Clone { - fn clone(&self) -> Self { - return Shell { - commands: self.commands.clone(), - default: self.default.clone(), - data: self.data.clone(), - prompt: self.prompt.clone(), - unclosed_prompt: self.unclosed_prompt.clone(), - history: self.history.clone() - }; - } -} - -/// Wrap the command history from a shell. -/// It has a maximum capacity, and when max capacity is reached, -/// less recent command is removed from history -#[derive(Clone)] -pub struct History { - history: Arc>>, - capacity: usize -} - -impl History { - /// Create a new history with the given capacity - fn new(capacity: usize) -> History { - return History { - history: Arc::new(Mutex::new(Vec::with_capacity(capacity))), - capacity: capacity - }; - } - - /// Push a command to the history, removing the oldest - /// one if maximum capacity has been reached - fn push(&self, cmd: String) { - let mut hist = self.history.lock().unwrap(); - if hist.len() >= self.capacity { - hist.remove(0); - } - hist.push(cmd); - } - - /// Print the history to stdout - pub fn print(&self, out: &mut T) { - let mut cnt = 0; - for s in &*self.history.lock().unwrap() { - writeln!(out, "{}: {}", cnt, s).expect("Cannot write to output"); - cnt += 1; - } - } - - /// Get a command from history by its index - pub fn get(&self, i: usize) -> Option { - return self.history.lock().unwrap().get(i).map(|s| s.clone()); - } -} - -mod builtins { - use std::str::FromStr; - use prettytable::row::Row; - use super::{Shell, ShellIO, ExecError, ExecResult}; - - pub type CmdFn = Box, &[&str]) -> ExecResult + Send + Sync>; - - pub struct Command { - pub name: String, - description: String, - nargs: usize, - func: CmdFn - } - - impl Command { - pub fn new(name: String, description: String, nargs: usize, func: CmdFn) -> Command { - return Command { - name: name, - description: description, - nargs: nargs, - func: func - }; - } - - pub fn help(&self) -> Row { - return row![self.name, ":", self.description]; - } - - pub fn run(&self, io: &mut ShellIO, shell: &mut Shell, args: &[&str]) -> ExecResult { - if args.len() < self.nargs { - return Err(ExecError::MissingArgs); - } - return (self.func)(io, shell, args); - } - } - - pub fn help_cmd() -> Command { - return Command::new("help".to_string(), "Print this help".to_string(), 0, Box::new(|io, shell, _| shell.print_help(io))); - } - - pub fn quit_cmd() -> Command { - return Command::new("quit".to_string(), "Quit".to_string(), 0, Box::new(|_, _, _| Err(ExecError::Quit))); - } - - pub fn history_cmd() -> Command { - return Command::new("history".to_string(), "Print commands history or run a command from it".to_string(), 0, Box::new(|io, shell, args| { - if args.len() > 0 { - let i = try!(usize::from_str(args[0])); - let cmd = try!(shell.get_history().get(i).ok_or(ExecError::InvalidHistory(i))); - return shell.eval(io, &cmd); - } else { - shell.get_history().print(io); - return Ok(()); - } - })); - } -} +//! A library for creating interactive command line shells +#[macro_use] extern crate prettytable; +use prettytable::Table; +use prettytable::format; + +use std::io; +use std::io::prelude::*; +use std::string::ToString; +use std::error::Error; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::sync::{Arc, Mutex}; + +use std::collections::BTreeMap; + +/// Command execution error +#[derive(Debug)] +pub enum ExecError { + /// Empty command provided + Empty, + /// Exit from the shell loop + Quit, + /// Some arguments are missing + MissingArgs, + /// The provided command is unknown + UnknownCommand(String), + /// The history index is not valid + InvalidHistory(usize), + /// Other error that may have happen during command execution + Other(Box), +} +use ExecError::*; + +impl fmt::Display for ExecError { + fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result { + return match self { + &Empty => write!(format, "No command provided"), + &Quit => write!(format, "Quit"), + &UnknownCommand(ref cmd) => write!(format, "Unknown Command {}", cmd), + &InvalidHistory(i) => write!(format, "Invalid history entry {}", i), + &MissingArgs => write!(format, "Not enough arguments"), + &Other(ref e) => write!(format, "{}", e) + }; + } +} + +// impl Error for ExecError { +// fn description(&self) -> &str { +// return match self { +// &Quit => "The command requested to quit", +// &UnknownCommand(..) => "The provided command is unknown", +// &MissingArgs => "Not enough arguments have been provided", +// &Other(..) => "Other error occured" +// }; +// } +// } + +impl From for ExecError { + fn from(e: E) -> ExecError { + return Other(Box::new(e)); + } +} + +/// Input / Output for shell execution +#[derive(Clone)] +pub struct ShellIO { + input: Arc>, + output: Arc> +} + +impl ShellIO { + /// Create a new Shell I/O wrapping provided Input and Output + pub fn new(input: I, output: O) -> ShellIO + where I: Read + Send + 'static, O: Write + Send + 'static + { + return ShellIO { + input: Arc::new(Mutex::new(input)), + output: Arc::new(Mutex::new(output)) + }; + } + + /// Create a new Shell I/O wrapping provided Read/Write io + pub fn new_io(io: T) -> ShellIO + where T: Read + Write + Send + 'static + { + let io = Arc::new(Mutex::new(io)); + return ShellIO { + input: io.clone(), + output: io + }; + } +} + +impl Read for ShellIO { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + return self.input.lock().expect("Cannot get handle to console input").read(buf); + } +} + +impl Write for ShellIO { + fn write(&mut self, buf: &[u8]) -> io::Result { + return self.output.lock().expect("Cannot get handle to console output").write(buf); + } + + fn flush(&mut self) -> io::Result<()> { + return self.output.lock().expect("Cannot get handle to console output").flush(); + } +} + +impl Default for ShellIO { + fn default() -> Self { + return Self::new(io::stdin(), io::stdout()); + } +} + + +/// Result from command execution +pub type ExecResult = Result<(), ExecError>; + +/// A shell +pub struct Shell { + commands: BTreeMap>>, + default: Arc, &str) -> ExecResult + Send + Sync>, + data: T, + prompt: String, + unclosed_prompt: String, + history: History +} + +impl Shell { + /// Create a new shell, wrapping `data`, using provided IO + pub fn new(data: T) -> Shell { + let mut sh = Shell { + commands: BTreeMap::new(), + default: Arc::new(|_, _, cmd| Err(UnknownCommand(cmd.to_string()))), + data: data, + prompt: String::from(">"), + unclosed_prompt: String::from(">"), + history: History::new(10), + }; + sh.register_command(builtins::help_cmd()); + sh.register_command(builtins::quit_cmd()); + sh.register_command(builtins::history_cmd()); + return sh; + } + + /// Get a mutable pointer to the inner data + pub fn data(&mut self) -> &mut T { + return &mut self.data; + } + + /// Change the current prompt + pub fn set_prompt(&mut self, prompt: String) { + self.prompt = prompt; + } + + /// Change the current unclosed prompt + pub fn set_unclosed_prompt(&mut self, prompt: String) { + self.unclosed_prompt = prompt; + } + + fn register_command(&mut self, cmd: builtins::Command) { + self.commands.insert(cmd.name.clone(), Arc::new(cmd)); + } + + // Set a custom default handler, invoked when a command is not found + pub fn set_default(&mut self, func: F) + where F: Fn(&mut ShellIO, &mut Shell, &str) -> ExecResult + Send + Sync + 'static + { + self.default = Arc::new(func); + } + + /// Register a shell command. + /// Shell commands get called with a reference to the current shell + pub fn new_shell_command(&mut self, name: S, description: S, nargs: usize, func: F) + where S: ToString, F: Fn(&mut ShellIO, &mut Shell, &[&str]) -> ExecResult + Send + Sync + 'static + { + self.register_command(builtins::Command::new(name.to_string(), description.to_string(), nargs, Box::new(func))); + } + + /// Register a command + pub fn new_command(&mut self, name: S, description: S, nargs: usize, func: F) + where S: ToString, F: Fn(&mut ShellIO, &mut T, &[&str]) -> ExecResult + Send + Sync + 'static + { + self.new_shell_command(name, description, nargs, move |io, sh, args| func(io, sh.data(), args)); + } + + /// Register a command that do not accept any argument + pub fn new_command_noargs(&mut self, name: S, description: S, func: F) + where S: ToString, F: Fn(&mut ShellIO, &mut T) -> ExecResult + Send + Sync + 'static + { + self.new_shell_command(name, description, 0, move |io, sh, _| func(io, sh.data())); + } + + /// Print the help to stdout + pub fn print_help(&self, io: &mut ShellIO) -> ExecResult { + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_CLEAN); + for cmd in self.commands.values() { + table.add_row(cmd.help()); + } + return table.print(io).map_err(|e| From::from(e)) + } + + /// Return the command history + pub fn get_history(&self) -> &History { + return &self.history; + } + + /// Evaluate a command line + pub fn eval(&mut self, io: &mut ShellIO, line: &str) -> ExecResult { + let mut splt = line.trim().split_whitespace(); + return match splt.next() { + None => Err(Empty), + Some(cmd) => match self.commands.get(cmd).map(|i| i.clone()) { + None => self.default.clone()(io, self, line), + Some(c) => c.run(io, self, &splt.collect::>()) + } + }; + } + + fn print_prompt(&self, io: &mut ShellIO, unclosed: bool) { + if unclosed { + write!(io, "{} ", self.unclosed_prompt).unwrap(); + } else { + write!(io, "{} ", self.prompt).unwrap(); + } + io.flush().unwrap(); + } + + /// Enter the shell main loop, exiting only when + /// the "quit" command is called + pub fn run_loop(&mut self, io: &mut ShellIO) { + self.print_prompt(io, false); + let stdin = io::BufReader::new(io.clone()); + let mut iter = stdin.lines().map(|l| l.unwrap()); + while let Some(mut line) = iter.next() { + while line.len() > 0 && &line[line.len()-1 ..] == "\\" { + self.print_prompt(io, true); + line.pop(); + line.push_str(&iter.next().unwrap()) + } + if let Err(e) = self.eval(io, &line) { + match e { + Empty => {}, + Quit => return, + e @ _ => writeln!(io, "Error : {}", e).unwrap() + }; + } else { + self.get_history().push(line); + } + self.print_prompt(io, false); + } + } +} + +impl Deref for Shell { + type Target = T; + fn deref(&self) -> &T { + return &self.data; + } +} + +impl DerefMut for Shell { + fn deref_mut(&mut self) -> &mut T { + return &mut self.data; + } +} + +impl Clone for Shell where T: Clone { + fn clone(&self) -> Self { + return Shell { + commands: self.commands.clone(), + default: self.default.clone(), + data: self.data.clone(), + prompt: self.prompt.clone(), + unclosed_prompt: self.unclosed_prompt.clone(), + history: self.history.clone() + }; + } +} + +/// Wrap the command history from a shell. +/// It has a maximum capacity, and when max capacity is reached, +/// less recent command is removed from history +#[derive(Clone)] +pub struct History { + history: Arc>>, + capacity: usize +} + +impl History { + /// Create a new history with the given capacity + fn new(capacity: usize) -> History { + return History { + history: Arc::new(Mutex::new(Vec::with_capacity(capacity))), + capacity: capacity + }; + } + + /// Push a command to the history, removing the oldest + /// one if maximum capacity has been reached + fn push(&self, cmd: String) { + let mut hist = self.history.lock().unwrap(); + if hist.len() >= self.capacity { + hist.remove(0); + } + hist.push(cmd); + } + + /// Print the history to stdout + pub fn print(&self, out: &mut T) { + let mut cnt = 0; + for s in &*self.history.lock().unwrap() { + writeln!(out, "{}: {}", cnt, s).expect("Cannot write to output"); + cnt += 1; + } + } + + /// Get a command from history by its index + pub fn get(&self, i: usize) -> Option { + return self.history.lock().unwrap().get(i).map(|s| s.clone()); + } +} + +mod builtins { + use std::str::FromStr; + use prettytable::row::Row; + use super::{Shell, ShellIO, ExecError, ExecResult}; + + pub type CmdFn = Box, &[&str]) -> ExecResult + Send + Sync>; + + pub struct Command { + pub name: String, + description: String, + nargs: usize, + func: CmdFn + } + + impl Command { + pub fn new(name: String, description: String, nargs: usize, func: CmdFn) -> Command { + return Command { + name: name, + description: description, + nargs: nargs, + func: func + }; + } + + pub fn help(&self) -> Row { + return row![self.name, ":", self.description]; + } + + pub fn run(&self, io: &mut ShellIO, shell: &mut Shell, args: &[&str]) -> ExecResult { + if args.len() < self.nargs { + return Err(ExecError::MissingArgs); + } + return (self.func)(io, shell, args); + } + } + + pub fn help_cmd() -> Command { + return Command::new("help".to_string(), "Print this help".to_string(), 0, Box::new(|io, shell, _| shell.print_help(io))); + } + + pub fn quit_cmd() -> Command { + return Command::new("quit".to_string(), "Quit".to_string(), 0, Box::new(|_, _, _| Err(ExecError::Quit))); + } + + pub fn history_cmd() -> Command { + return Command::new("history".to_string(), "Print commands history or run a command from it".to_string(), 0, Box::new(|io, shell, args| { + if args.len() > 0 { + let i = try!(usize::from_str(args[0])); + let cmd = try!(shell.get_history().get(i).ok_or(ExecError::InvalidHistory(i))); + return shell.eval(io, &cmd); + } else { + shell.get_history().print(io); + return Ok(()); + } + })); + } +}