diff --git a/tools/cli/src/mentat_cli/command_parser.rs b/tools/cli/src/mentat_cli/command_parser.rs index aa81e4d7..36d4912f 100644 --- a/tools/cli/src/mentat_cli/command_parser.rs +++ b/tools/cli/src/mentat_cli/command_parser.rs @@ -13,7 +13,6 @@ use combine::{ eof, look_ahead, many1, - parser, satisfy, sep_end_by, token, @@ -29,8 +28,6 @@ use combine::combinator::{ try }; -use combine::primitives::Consumed; - use errors as cli; use edn; @@ -42,14 +39,17 @@ pub static LONG_QUERY_COMMAND: &'static str = &"query"; pub static SHORT_QUERY_COMMAND: &'static str = &"q"; pub static LONG_TRANSACT_COMMAND: &'static str = &"transact"; pub static SHORT_TRANSACT_COMMAND: &'static str = &"t"; +pub static LONG_EXIT_COMMAND: &'static str = &"exit"; +pub static SHORT_EXIT_COMMAND: &'static str = &"e"; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Command { - Transact(String), - Query(String), + Close, + Exit, Help(Vec), Open(String), - Close, + Query(String), + Transact(String), } impl Command { @@ -65,7 +65,8 @@ impl Command { }, &Command::Help(_) | &Command::Open(_) | - &Command::Close => true + &Command::Close | + &Command::Exit => true } } @@ -86,6 +87,9 @@ impl Command { &Command::Close => { format!(".{}", CLOSE_COMMAND) }, + &Command::Exit => { + format!(".{}", LONG_EXIT_COMMAND) + }, } } } @@ -112,18 +116,29 @@ pub fn command(s: &str) -> Result { } Ok(Command::Open(args[0].clone())) }); + + let no_arg_parser = || arguments() + .skip(spaces()) + .skip(eof()); let close_parser = string(CLOSE_COMMAND) - .with(arguments()) - .skip(spaces()) - .skip(eof()) + .with(no_arg_parser()) .map(|args| { - if args.len() > 0 { + if !args.is_empty() { bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); } Ok(Command::Close) }); + let exit_parser = try(string(LONG_EXIT_COMMAND)).or(try(string(SHORT_EXIT_COMMAND))) + .with(no_arg_parser()) + .map(|args| { + if !args.is_empty() { + bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); + } + Ok(Command::Exit) + }); + let edn_arg_parser = || spaces() .with(look_ahead(string("[").or(string("{"))) .with(many1::, _>(try(any()))) @@ -146,10 +161,11 @@ pub fn command(s: &str) -> Result { spaces() .skip(token('.')) - .with(choice::<[&mut Parser>; 5], _> + .with(choice::<[&mut Parser>; 6], _> ([&mut try(help_parser), &mut try(open_parser), &mut try(close_parser), + &mut try(exit_parser), &mut try(query_parser), &mut try(transact_parser)])) .parse(s) @@ -294,6 +310,43 @@ mod tests { } } + #[test] + fn test_exit_parser_with_args() { + let input = ".exit arg1"; + let err = command(&input).expect_err("Expected an error"); + assert_eq!(err.to_string(), format!("Invalid command {:?}", input)); + } + + #[test] + fn test_exit_parser_no_args() { + let input = ".exit"; + let cmd = command(&input).expect("Expected exit command"); + match cmd { + Command::Exit => assert!(true), + _ => assert!(false) + } + } + + #[test] + fn test_exit_parser_no_args_trailing_whitespace() { + let input = ".exit "; + let cmd = command(&input).expect("Expected exit command"); + match cmd { + Command::Exit => assert!(true), + _ => assert!(false) + } + } + + #[test] + fn test_exit_parser_short_command() { + let input = ".e"; + let cmd = command(&input).expect("Expected exit command"); + match cmd { + Command::Exit => assert!(true), + _ => assert!(false) + } + } + #[test] fn test_query_parser_complete_edn() { let input = ".q [:find ?x :where [?x foo/bar ?y]]"; diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index 9f84060c..5c3cb790 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -8,7 +8,10 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -use std::collections::HashMap; +use std::collections::HashMap; +use std::process; + +use error_chain::ChainedError; use mentat::query::QueryResults; use mentat_core::TypedValue; @@ -21,6 +24,8 @@ use command_parser::{ SHORT_QUERY_COMMAND, LONG_TRANSACT_COMMAND, SHORT_TRANSACT_COMMAND, + LONG_EXIT_COMMAND, + SHORT_EXIT_COMMAND, }; use input::InputReader; use input::InputResult::{ @@ -37,6 +42,8 @@ use store::{ lazy_static! { static ref COMMAND_HELP: HashMap<&'static str, &'static str> = { let mut map = HashMap::new(); + map.insert(LONG_EXIT_COMMAND, "Close the current database and exit the REPL."); + map.insert(SHORT_EXIT_COMMAND, "Shortcut for `.exit`. Close the current database and exit the REPL."); map.insert(HELP_COMMAND, "Show help for commands."); map.insert(OPEN_COMMAND, "Open a database at path."); map.insert(LONG_QUERY_COMMAND, "Execute a query against the current open database."); @@ -103,18 +110,25 @@ impl Repl { Err(e) => println!("{}", e.to_string()) }; }, - Command::Close => { - let old_db_name = self.store.db_name.clone(); - match self.store.close() { - Ok(_) => println!("Database {:?} closed", db_output_name(&old_db_name)), - Err(e) => println!("{}", e.to_string()) - }; - }, + Command::Close => self.close(), Command::Query(query) => self.execute_query(query), Command::Transact(transaction) => self.execute_transact(transaction), + Command::Exit => { + self.close(); + println!("Exiting..."); + process::exit(0); + } } } + fn close(&mut self) { + let old_db_name = self.store.db_name.clone(); + match self.store.close() { + Ok(_) => println!("Database {:?} closed", db_output_name(&old_db_name)), + Err(e) => println!("{}", e.display()) + }; + } + fn help_command(&self, args: Vec) { if args.is_empty() { for (cmd, msg) in COMMAND_HELP.iter() {