use std::cell::RefCell;
use std::rc::Rc;
use rustyline::error::ReadlineError;
use crate::command::{
builtin::{ExitCommand, HelpCommand, HelpTreeCommand, HistoryCommand},
BaseCommand, Command,
};
use crate::command_set::CommandSet;
use crate::error::ShiError;
use crate::parser::{CommandType, Outcome, Parser};
use crate::readline::Readline;
use crate::Result;
pub struct Shell<'a, S> {
prompt: &'a str,
pub(crate) cmds: Rc<RefCell<CommandSet<'a, S>>>,
pub(crate) builtins: Rc<CommandSet<'a, Self>>,
pub(crate) rl: Readline<'a, S>,
parser: Parser,
history_file: Option<&'a str>,
state: S,
pub(crate) terminate: bool,
}
impl<'a> Shell<'a, ()> {
pub fn new(prompt: &'a str) -> Shell<()> {
let cmds = Rc::new(RefCell::new(CommandSet::new()));
let builtins = Rc::new(Shell::build_builtins());
Shell {
prompt,
rl: Readline::new(Parser::new(), cmds.clone(), builtins.clone()),
parser: Parser::new(),
cmds,
builtins,
history_file: None,
state: (),
terminate: false,
}
}
}
impl<'a, S> Shell<'a, S> {
fn build_builtins() -> CommandSet<'a, Shell<'a, S>>
where
S: 'a,
{
let mut builtins: CommandSet<'a, Shell<'a, S>> = CommandSet::new();
builtins.add(Command::new_leaf(HelpCommand::new()));
builtins.add(Command::new_leaf(HelpTreeCommand::new()));
builtins.add(Command::new_leaf(ExitCommand::new()));
builtins.add(Command::new_leaf(HistoryCommand::new()));
builtins
}
pub fn new_with_state(prompt: &'a str, state: S) -> Shell<S>
where
S: 'a,
{
let cmds = Rc::new(RefCell::new(CommandSet::new()));
let builtins = Rc::new(Shell::build_builtins());
Shell {
prompt,
rl: Readline::new(Parser::new(), cmds.clone(), builtins.clone()),
parser: Parser::new(),
cmds,
builtins,
history_file: None,
state,
terminate: false,
}
}
pub fn register(&mut self, cmd: Command<'a, S>) -> Result<()> {
if self.cmds.borrow().contains(cmd.name()) {
return Err(ShiError::AlreadyRegistered {
cmd: cmd.name().to_string(),
});
}
self.cmds.borrow_mut().add(cmd);
Ok(())
}
pub fn set_and_load_history_file(&mut self, history_file: &'a str) -> Result<()> {
self.rl.load_history(history_file)?;
self.history_file = Some(history_file);
Ok(())
}
pub fn save_history(&mut self) -> Result<()> {
if let Some(history_file) = self.history_file {
self.rl.save_history(history_file)?;
}
Ok(())
}
pub(crate) fn parse<'b>(&mut self, line: &'b str) -> Outcome<'b> {
self.parser.parse(line, &self.cmds.borrow(), &self.builtins)
}
pub fn eval(&mut self, line: &str) -> Result<String> {
self.rl.add_history_entry(line);
let outcome = self.parse(line);
if !outcome.complete {
return Err(outcome
.error()
.expect("incomplete parse, but failed to produce an error")); }
match outcome.cmd_type {
CommandType::Custom => {
if let Some(base_cmd_name) = outcome.cmd_path.first() {
if let Some(base_cmd) = self.cmds.borrow().get(base_cmd_name) {
let args: Vec<String> =
line.split(' ').skip(1).map(|s| s.to_string()).collect();
base_cmd.validate_args(&args)?;
return base_cmd.execute(&mut self.state, &args);
}
}
Err(ShiError::UnrecognizedCommand {
got: line.to_string(),
})
}
CommandType::Builtin => {
if let Some(base_cmd_name) = outcome.cmd_path.first() {
if let Some(base_cmd) = self.builtins.clone().get(base_cmd_name) {
let args: Vec<String> =
line.split(' ').skip(1).map(|s| s.to_string()).collect();
base_cmd.validate_args(&args)?;
return base_cmd.execute(self, &args);
}
}
Err(ShiError::UnrecognizedCommand {
got: line.to_string(),
})
}
CommandType::Unknown => Err(outcome
.error()
.expect("parsed an Unknown, but failed to produce an error")), }
}
pub fn run(&mut self) -> Result<()> {
while !self.terminate {
let input = self.rl.readline(self.prompt);
match input {
Ok(line) => match self.eval(&line) {
Ok(output) => println!("{}", output),
Err(err) => println!("Error: {}", err),
},
Err(ReadlineError::Interrupted) => {
println!("-> CTRL+C; bye.");
break;
}
Err(ReadlineError::Eof) => {
println!("-> CTRL+D; bye.");
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
}
self.save_history()?;
Ok(())
}
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::Result;
use crate::{cmd, parent};
use pretty_assertions::assert_eq;
#[test]
fn issue6() -> Result<()> {
let mut shell = Shell::new("| ");
shell.register(parent!(
"server",
cmd!("listen", "Start listening on the given port", |_, args| {
Ok(format!("start: {:?}", args))
}),
cmd!("unlisten", "stop listening", |_, args| {
Ok(format!("stop: {:?}", args))
})
))?;
let output = shell.eval("server listen")?;
assert_eq!(output, "start: []");
let output = shell.eval("server listen foo")?;
assert_eq!(output, "start: [\"foo\"]");
let output = shell.eval("server unlisten")?;
assert_eq!(output, "stop: []");
let output = shell.eval("server unlisten foo")?;
assert_eq!(output, "stop: [\"foo\"]");
Ok(())
}
}