Exit CLI (#457) (#484) r-rnewman

* Implement exit command for cli tool

* Address review comments r=rnewman

* Include exit commands in help
This commit is contained in:
Emily Toop 2017-06-22 12:52:43 +01:00 committed by GitHub
parent 46fc1615fb
commit ecc926086a
2 changed files with 87 additions and 20 deletions

View file

@ -13,7 +13,6 @@ use combine::{
eof, eof,
look_ahead, look_ahead,
many1, many1,
parser,
satisfy, satisfy,
sep_end_by, sep_end_by,
token, token,
@ -29,8 +28,6 @@ use combine::combinator::{
try try
}; };
use combine::primitives::Consumed;
use errors as cli; use errors as cli;
use edn; use edn;
@ -42,14 +39,17 @@ pub static LONG_QUERY_COMMAND: &'static str = &"query";
pub static SHORT_QUERY_COMMAND: &'static str = &"q"; pub static SHORT_QUERY_COMMAND: &'static str = &"q";
pub static LONG_TRANSACT_COMMAND: &'static str = &"transact"; pub static LONG_TRANSACT_COMMAND: &'static str = &"transact";
pub static SHORT_TRANSACT_COMMAND: &'static str = &"t"; 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)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Command { pub enum Command {
Transact(String), Close,
Query(String), Exit,
Help(Vec<String>), Help(Vec<String>),
Open(String), Open(String),
Close, Query(String),
Transact(String),
} }
impl Command { impl Command {
@ -65,7 +65,8 @@ impl Command {
}, },
&Command::Help(_) | &Command::Help(_) |
&Command::Open(_) | &Command::Open(_) |
&Command::Close => true &Command::Close |
&Command::Exit => true
} }
} }
@ -86,6 +87,9 @@ impl Command {
&Command::Close => { &Command::Close => {
format!(".{}", CLOSE_COMMAND) format!(".{}", CLOSE_COMMAND)
}, },
&Command::Exit => {
format!(".{}", LONG_EXIT_COMMAND)
},
} }
} }
} }
@ -112,18 +116,29 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
} }
Ok(Command::Open(args[0].clone())) Ok(Command::Open(args[0].clone()))
}); });
let no_arg_parser = || arguments()
.skip(spaces())
.skip(eof());
let close_parser = string(CLOSE_COMMAND) let close_parser = string(CLOSE_COMMAND)
.with(arguments()) .with(no_arg_parser())
.skip(spaces())
.skip(eof())
.map(|args| { .map(|args| {
if args.len() > 0 { if !args.is_empty() {
bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) );
} }
Ok(Command::Close) 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() let edn_arg_parser = || spaces()
.with(look_ahead(string("[").or(string("{"))) .with(look_ahead(string("[").or(string("{")))
.with(many1::<Vec<_>, _>(try(any()))) .with(many1::<Vec<_>, _>(try(any())))
@ -146,10 +161,11 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
spaces() spaces()
.skip(token('.')) .skip(token('.'))
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 5], _> .with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 6], _>
([&mut try(help_parser), ([&mut try(help_parser),
&mut try(open_parser), &mut try(open_parser),
&mut try(close_parser), &mut try(close_parser),
&mut try(exit_parser),
&mut try(query_parser), &mut try(query_parser),
&mut try(transact_parser)])) &mut try(transact_parser)]))
.parse(s) .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] #[test]
fn test_query_parser_complete_edn() { fn test_query_parser_complete_edn() {
let input = ".q [:find ?x :where [?x foo/bar ?y]]"; let input = ".q [:find ?x :where [?x foo/bar ?y]]";

View file

@ -8,7 +8,10 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // 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::query::QueryResults;
use mentat_core::TypedValue; use mentat_core::TypedValue;
@ -21,6 +24,8 @@ use command_parser::{
SHORT_QUERY_COMMAND, SHORT_QUERY_COMMAND,
LONG_TRANSACT_COMMAND, LONG_TRANSACT_COMMAND,
SHORT_TRANSACT_COMMAND, SHORT_TRANSACT_COMMAND,
LONG_EXIT_COMMAND,
SHORT_EXIT_COMMAND,
}; };
use input::InputReader; use input::InputReader;
use input::InputResult::{ use input::InputResult::{
@ -37,6 +42,8 @@ use store::{
lazy_static! { lazy_static! {
static ref COMMAND_HELP: HashMap<&'static str, &'static str> = { static ref COMMAND_HELP: HashMap<&'static str, &'static str> = {
let mut map = HashMap::new(); 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(HELP_COMMAND, "Show help for commands.");
map.insert(OPEN_COMMAND, "Open a database at path."); map.insert(OPEN_COMMAND, "Open a database at path.");
map.insert(LONG_QUERY_COMMAND, "Execute a query against the current open database."); 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()) Err(e) => println!("{}", e.to_string())
}; };
}, },
Command::Close => { Command::Close => self.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::Query(query) => self.execute_query(query), Command::Query(query) => self.execute_query(query),
Command::Transact(transaction) => self.execute_transact(transaction), 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<String>) { fn help_command(&self, args: Vec<String>) {
if args.is_empty() { if args.is_empty() {
for (cmd, msg) in COMMAND_HELP.iter() { for (cmd, msg) in COMMAND_HELP.iter() {