From 39cf26aa768f5514571c21ecc2c0f1c3191aa9f1 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Tue, 20 Feb 2018 16:20:07 -0800 Subject: [PATCH] Add import command. (#456) r=emily --- tools/cli/src/mentat_cli/command_parser.rs | 33 ++++++++++++++++++++-- tools/cli/src/mentat_cli/lib.rs | 5 ++++ tools/cli/src/mentat_cli/repl.rs | 20 +++++++++++-- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/tools/cli/src/mentat_cli/command_parser.rs b/tools/cli/src/mentat_cli/command_parser.rs index 91b76b8e..f542703c 100644 --- a/tools/cli/src/mentat_cli/command_parser.rs +++ b/tools/cli/src/mentat_cli/command_parser.rs @@ -45,6 +45,8 @@ pub static SCHEMA_COMMAND: &'static str = &"schema"; pub static LONG_TIMER_COMMAND: &'static str = &"timer"; pub static LONG_TRANSACT_COMMAND: &'static str = &"transact"; pub static SHORT_TRANSACT_COMMAND: &'static str = &"t"; +pub static LONG_IMPORT_COMMAND: &'static str = &"import"; +pub static SHORT_IMPORT_COMMAND: &'static str = &"i"; pub static LONG_EXIT_COMMAND: &'static str = &"exit"; pub static SHORT_EXIT_COMMAND: &'static str = &"e"; pub static LONG_QUERY_EXPLAIN_COMMAND: &'static str = &"explain_query"; @@ -64,6 +66,7 @@ pub enum Command { Sync(Vec), Timer(bool), Transact(String), + Import(String), QueryExplain(String), } @@ -85,6 +88,7 @@ impl Command { &Command::OpenEmpty(_) | &Command::Close | &Command::Exit | + &Command::Import(_) | &Command::Sync(_) | &Command::Cache(_, _) | &Command::Schema => true @@ -94,6 +98,7 @@ impl Command { pub fn is_timed(&self) -> bool { match self { &Command::Query(_) | + &Command::Import(_) | &Command::Transact(_) => true, &Command::QueryExplain(_) | &Command::Timer(_) | @@ -113,6 +118,9 @@ impl Command { &Command::Query(ref args) => { format!(".{} {}", LONG_QUERY_COMMAND, args) }, + &Command::Import(ref args) => { + format!(".{} {}", LONG_IMPORT_COMMAND, args) + }, &Command::Transact(ref args) => { format!(".{} {}", LONG_TRANSACT_COMMAND, args) }, @@ -151,6 +159,7 @@ impl Command { } pub fn command(s: &str) -> Result { + let path = || many1::(satisfy(|c: char| !c.is_whitespace())); let argument = || many1::(satisfy(|c: char| !c.is_whitespace())); let arguments = || sep_end_by::, _, _>(many1(satisfy(|c: char| !c.is_whitespace())), many1::, _>(space())).expected("arguments"); @@ -192,7 +201,7 @@ pub fn command(s: &str) -> Result { } Ok(Command::Open(args[0].clone())) }); - + let open_empty_parser = string(OPEN_EMPTY_COMMAND) .with(spaces()) .with(arguments()) @@ -264,9 +273,16 @@ pub fn command(s: &str) -> Result { Ok(Command::Query(x)) }); + let import_parser = try(string(LONG_IMPORT_COMMAND)).or(try(string(SHORT_IMPORT_COMMAND))) + .with(spaces()) + .with(path()) + .map(|x| { + Ok(Command::Import(x)) + }); + let transact_parser = try(string(LONG_TRANSACT_COMMAND)).or(try(string(SHORT_TRANSACT_COMMAND))) .with(edn_arg_parser()) - .map( |x| { + .map(|x| { Ok(Command::Transact(x)) }); @@ -278,8 +294,9 @@ pub fn command(s: &str) -> Result { }); spaces() .skip(token('.')) - .with(choice::<[&mut Parser>; 12], _> + .with(choice::<[&mut Parser>; 13], _> ([&mut try(help_parser), + &mut try(import_parser), &mut try(timer_parser), &mut try(cache_parser), &mut try(open_parser), @@ -564,6 +581,16 @@ mod tests { assert_eq!(err.to_string(), format!("Invalid command {:?}", input)); } + #[test] + fn test_import_parser() { + let input = ".import /foo/bar/"; + let cmd = command(&input).expect("Expected import command"); + match cmd { + Command::Import(path) => assert_eq!(path, "/foo/bar/"), + _ => panic!("Wrong command!") + } + } + #[test] fn test_transact_parser_complete_edn() { let input = ".t [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; diff --git a/tools/cli/src/mentat_cli/lib.rs b/tools/cli/src/mentat_cli/lib.rs index be08ce23..343e5b6e 100644 --- a/tools/cli/src/mentat_cli/lib.rs +++ b/tools/cli/src/mentat_cli/lib.rs @@ -53,6 +53,7 @@ pub fn run() -> i32 { opts.optflag("h", "help", "Print this help message and exit"); opts.optmulti("q", "query", "Execute a query on startup. Queries are executed after any transacts.", "QUERY"); opts.optmulti("t", "transact", "Execute a transact on startup. Transacts are executed before queries.", "TRANSACT"); + opts.optmulti("i", "import", "Execute an import on startup. Imports are executed before queries.", "PATH"); opts.optflag("v", "version", "Print version and exit"); let matches = match opts.parse(&args[1..]) { @@ -84,6 +85,10 @@ pub fn run() -> i32 { last_arg = None; Some(command_parser::Command::Query(arg.clone())) }, + Some("-i") => { + last_arg = None; + Some(command_parser::Command::Import(arg.clone())) + }, Some("-t") => { last_arg = None; Some(command_parser::Command::Transact(arg.clone())) diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index fb321dc0..4fac74ec 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -8,7 +8,7 @@ // 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::BTreeMap; use std::io::Write; use std::process; @@ -46,6 +46,7 @@ use command_parser::{ SHORT_QUERY_COMMAND, SCHEMA_COMMAND, SYNC_COMMAND, + LONG_IMPORT_COMMAND, LONG_TRANSACT_COMMAND, SHORT_TRANSACT_COMMAND, LONG_EXIT_COMMAND, @@ -63,8 +64,8 @@ use input::InputResult::{ }; lazy_static! { - static ref COMMAND_HELP: HashMap<&'static str, &'static str> = { - let mut map = HashMap::new(); + static ref COMMAND_HELP: BTreeMap<&'static str, &'static str> = { + let mut map = BTreeMap::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."); @@ -76,6 +77,7 @@ lazy_static! { map.insert(SYNC_COMMAND, "Synchronize database against a Sync Server URL for a provided user UUID."); map.insert(LONG_TRANSACT_COMMAND, "Execute a transact against the current open database."); map.insert(SHORT_TRANSACT_COMMAND, "Shortcut for `.transact`. Execute a transact against the current open database."); + map.insert(LONG_IMPORT_COMMAND, "Transact the contents of a file against the current open database."); map.insert(LONG_QUERY_EXPLAIN_COMMAND, "Show the SQL and query plan that would be executed for a given query."); map.insert(SHORT_QUERY_EXPLAIN_COMMAND, "Shortcut for `.explain_query`. Show the SQL and query plan that would be executed for a given query."); @@ -210,6 +212,7 @@ impl Repl { Err(e) => eprintln!("{}", e.to_string()), }; }, + Command::Import(path) => self.execute_import(path), Command::Sync(args) => { match self.store.sync(&args[0], &args[1]) { Ok(_) => println!("Synced!"), @@ -249,6 +252,17 @@ impl Repl { } } + fn execute_import(&mut self, path: T) + where T: Into { + use ::std::io::Read; + let path = path.into(); + let mut content: String = "".to_string(); + match ::std::fs::File::open(path.clone()).and_then(|mut f| f.read_to_string(&mut content)) { + Ok(_) => self.execute_transact(content), + Err(e) => eprintln!("Error reading file {}: {}", path, e) + } + } + fn open(&mut self, path: T) -> ::mentat::errors::Result<()> where T: Into { let path = path.into();