Skip to content

Commit

Permalink
Refactored the way I/O are managed
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Henri Symoneaux committed Jun 19, 2016
1 parent d7c4084 commit 8e27852
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 47 deletions.
4 changes: 2 additions & 2 deletions examples/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
extern crate shrust;
use shrust::Shell;
use shrust::{Shell, ShellIO};
use std::io::prelude::*;

use std::collections::HashMap;
Expand Down Expand Up @@ -35,5 +35,5 @@ fn main() {
Ok(())
});

shell.run_loop();
shell.run_loop(&mut ShellIO::default());
}
5 changes: 2 additions & 3 deletions examples/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ fn main() {
for sock in serv.incoming() {
let sock = sock.unwrap();
let mut shell = shell.clone();
let io = ShellIO::new_io(sock);
shell.set_io(io);
thread::spawn(move || shell.run_loop());
let mut io = ShellIO::new_io(sock);
thread::spawn(move || shell.run_loop(&mut io));
}
}
70 changes: 28 additions & 42 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,16 @@ pub type ExecResult = Result<(), ExecError>;
/// A shell
pub struct Shell<T> {
commands: BTreeMap<String, Arc<builtins::Command<T>>>,
io: ShellIO,
data: T,
prompt: String,
history: History
}

impl <T> Shell<T> {
/// Create a new shell, wrapping `data`, using provided IO
pub fn new_io(data: T, io: ShellIO) -> Shell<T> {
pub fn new(data: T) -> Shell<T> {
let mut sh = Shell {
commands: BTreeMap::new(),
io: io,
data: data,
prompt: String::from(">"),
history: History::new(10),
Expand All @@ -144,11 +142,6 @@ impl <T> Shell<T> {
return sh;
}

/// Create a new shell, wrapping `data`, using standard input/outpu
pub fn new(data: T) -> Shell<T> {
return Shell::new_io(data, Default::default());
}

/// Get a mutable pointer to the inner data
pub fn data(&mut self) -> &mut T {
return &mut self.data;
Expand All @@ -159,45 +152,40 @@ impl <T> Shell<T> {
self.prompt = prompt;
}

///Configure Input / Output for shell
pub fn set_io(&mut self, io: ShellIO) {
self.io = io;
}

fn register_command(&mut self, cmd: builtins::Command<T>) {
self.commands.insert(cmd.name.clone(), Arc::new(cmd));
}

/// Register a shell command.
/// Shell commands get called with a reference to the current shell
pub fn new_shell_command<S, F>(&mut self, name: S, description: S, nargs: usize, func: F)
where S: ToString, F: Fn(&mut Shell<T>, &[&str]) -> ExecResult + Send + Sync + 'static
where S: ToString, F: Fn(&mut ShellIO, &mut Shell<T>, &[&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<S, F>(&mut self, name: S, description: S, nargs: usize, func: F)
where S: ToString, F: Fn(ShellIO, &mut T, &[&str]) -> ExecResult + Send + Sync + 'static
where S: ToString, F: Fn(&mut ShellIO, &mut T, &[&str]) -> ExecResult + Send + Sync + 'static
{
self.new_shell_command(name, description, nargs, move |sh, args| func(sh.io.clone(), sh.data(), args));
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<S, F>(&mut self, name: S, description: S, func: F)
where S: ToString, F: Fn(ShellIO, &mut T) -> ExecResult + Send + Sync + 'static
where S: ToString, F: Fn(&mut ShellIO, &mut T) -> ExecResult + Send + Sync + 'static
{
self.new_shell_command(name, description, 0, move |sh, _| func(sh.io.clone(), sh.data()));
self.new_shell_command(name, description, 0, move |io, sh, _| func(io, sh.data()));
}

/// Print the help to stdout
pub fn print_help(&mut self) -> ExecResult {
pub fn print_help(&mut 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(&mut self.io).map_err(|e| From::from(e))
return table.print(io).map_err(|e| From::from(e))
}

/// Return the command history
Expand All @@ -206,39 +194,39 @@ impl <T> Shell<T> {
}

/// Evaluate a command line
pub fn eval(&mut self, line: &str) -> ExecResult {
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 => Err(UnknownCommand(cmd.to_string())),
Some(c) => c.run(self, &splt.collect::<Vec<&str>>())
Some(c) => c.run(io, self, &splt.collect::<Vec<&str>>())
}
};
}

fn print_prompt(&mut self) {
write!(self.io, "{}", self.prompt).unwrap();
self.io.flush().unwrap();
fn print_prompt(&mut self, io: &mut ShellIO) {
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) {
self.print_prompt();
let stdin = io::BufReader::new(self.io.clone());
pub fn run_loop(&mut self, io: &mut ShellIO) {
self.print_prompt(io);
let stdin = io::BufReader::new(io.clone());

for line in stdin.lines().map(|l| l.unwrap()) {
if let Err(e) = self.eval(&line) {
if let Err(e) = self.eval(io, &line) {
match e {
Empty => {},
Quit => return,
e @ _ => writeln!(self.io, "Error : {}", e).unwrap()
e @ _ => writeln!(io, "Error : {}", e).unwrap()
};
} else {
self.get_history().push(line);
}
self.print_prompt();
self.print_prompt(io);
}
}
}
Expand All @@ -260,7 +248,6 @@ impl <T> Clone for Shell<T> where T: Clone {
fn clone(&self) -> Self {
return Shell {
commands: self.commands.clone(),
io: self.io.clone(),
data: self.data.clone(),
prompt: self.prompt.clone(),
history: self.history.clone()
Expand Down Expand Up @@ -314,9 +301,9 @@ impl History {
mod builtins {
use std::str::FromStr;
use prettytable::row::Row;
use super::{Shell, ExecError, ExecResult};
use super::{Shell, ShellIO, ExecError, ExecResult};

pub type CmdFn<T> = Box<Fn(&mut Shell<T>, &[&str]) -> ExecResult + Send + Sync>;
pub type CmdFn<T> = Box<Fn(&mut ShellIO, &mut Shell<T>, &[&str]) -> ExecResult + Send + Sync>;

pub struct Command<T> {
pub name: String,
Expand All @@ -339,31 +326,30 @@ mod builtins {
return row![self.name, ":", self.description];
}

pub fn run(&self, shell: &mut Shell<T>, args: &[&str]) -> ExecResult {
pub fn run(&self, io: &mut ShellIO, shell: &mut Shell<T>, args: &[&str]) -> ExecResult {
if args.len() < self.nargs {
return Err(ExecError::MissingArgs);
}
return (self.func)(shell, args);
return (self.func)(io, shell, args);
}
}

pub fn help_cmd<T>() -> Command<T> {
return Command::new("help".to_string(), "Print this help".to_string(), 0, Box::new(|shell, _| shell.print_help()));
return Command::new("help".to_string(), "Print this help".to_string(), 0, Box::new(|io, shell, _| shell.print_help(io)));
}

pub fn quit_cmd<T>() -> Command<T> {
return Command::new("quit".to_string(), "Quit".to_string(), 0, Box::new(|_, _| Err(ExecError::Quit)));
return Command::new("quit".to_string(), "Quit".to_string(), 0, Box::new(|_, _, _| Err(ExecError::Quit)));
}

pub fn history_cmd<T>() -> Command<T> {
return Command::new("history".to_string(), "Print commands history or run a command from it".to_string(), 0, Box::new(|shell, args| {
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(&cmd);
return shell.eval(io, &cmd);
} else {
let mut io = shell.io.clone();
shell.get_history().print(&mut io);
shell.get_history().print(io);
return Ok(());
}
}));
Expand Down

0 comments on commit 8e27852

Please sign in to comment.