Add import command. (#456) r=emily

This commit is contained in:
Richard Newman 2018-02-20 16:20:07 -08:00
parent c53da08a00
commit 39cf26aa76
3 changed files with 52 additions and 6 deletions

View file

@ -45,6 +45,8 @@ pub static SCHEMA_COMMAND: &'static str = &"schema";
pub static LONG_TIMER_COMMAND: &'static str = &"timer"; pub static LONG_TIMER_COMMAND: &'static str = &"timer";
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_IMPORT_COMMAND: &'static str = &"import";
pub static SHORT_IMPORT_COMMAND: &'static str = &"i";
pub static LONG_EXIT_COMMAND: &'static str = &"exit"; pub static LONG_EXIT_COMMAND: &'static str = &"exit";
pub static SHORT_EXIT_COMMAND: &'static str = &"e"; pub static SHORT_EXIT_COMMAND: &'static str = &"e";
pub static LONG_QUERY_EXPLAIN_COMMAND: &'static str = &"explain_query"; pub static LONG_QUERY_EXPLAIN_COMMAND: &'static str = &"explain_query";
@ -64,6 +66,7 @@ pub enum Command {
Sync(Vec<String>), Sync(Vec<String>),
Timer(bool), Timer(bool),
Transact(String), Transact(String),
Import(String),
QueryExplain(String), QueryExplain(String),
} }
@ -85,6 +88,7 @@ impl Command {
&Command::OpenEmpty(_) | &Command::OpenEmpty(_) |
&Command::Close | &Command::Close |
&Command::Exit | &Command::Exit |
&Command::Import(_) |
&Command::Sync(_) | &Command::Sync(_) |
&Command::Cache(_, _) | &Command::Cache(_, _) |
&Command::Schema => true &Command::Schema => true
@ -94,6 +98,7 @@ impl Command {
pub fn is_timed(&self) -> bool { pub fn is_timed(&self) -> bool {
match self { match self {
&Command::Query(_) | &Command::Query(_) |
&Command::Import(_) |
&Command::Transact(_) => true, &Command::Transact(_) => true,
&Command::QueryExplain(_) | &Command::QueryExplain(_) |
&Command::Timer(_) | &Command::Timer(_) |
@ -113,6 +118,9 @@ impl Command {
&Command::Query(ref args) => { &Command::Query(ref args) => {
format!(".{} {}", LONG_QUERY_COMMAND, args) format!(".{} {}", LONG_QUERY_COMMAND, args)
}, },
&Command::Import(ref args) => {
format!(".{} {}", LONG_IMPORT_COMMAND, args)
},
&Command::Transact(ref args) => { &Command::Transact(ref args) => {
format!(".{} {}", LONG_TRANSACT_COMMAND, args) format!(".{} {}", LONG_TRANSACT_COMMAND, args)
}, },
@ -151,6 +159,7 @@ impl Command {
} }
pub fn command(s: &str) -> Result<Command, cli::Error> { pub fn command(s: &str) -> Result<Command, cli::Error> {
let path = || many1::<String, _>(satisfy(|c: char| !c.is_whitespace()));
let argument = || many1::<String, _>(satisfy(|c: char| !c.is_whitespace())); let argument = || many1::<String, _>(satisfy(|c: char| !c.is_whitespace()));
let arguments = || sep_end_by::<Vec<_>, _, _>(many1(satisfy(|c: char| !c.is_whitespace())), many1::<Vec<_>, _>(space())).expected("arguments"); let arguments = || sep_end_by::<Vec<_>, _, _>(many1(satisfy(|c: char| !c.is_whitespace())), many1::<Vec<_>, _>(space())).expected("arguments");
@ -264,9 +273,16 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
Ok(Command::Query(x)) 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))) let transact_parser = try(string(LONG_TRANSACT_COMMAND)).or(try(string(SHORT_TRANSACT_COMMAND)))
.with(edn_arg_parser()) .with(edn_arg_parser())
.map( |x| { .map(|x| {
Ok(Command::Transact(x)) Ok(Command::Transact(x))
}); });
@ -278,8 +294,9 @@ 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>>; 12], _> .with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 13], _>
([&mut try(help_parser), ([&mut try(help_parser),
&mut try(import_parser),
&mut try(timer_parser), &mut try(timer_parser),
&mut try(cache_parser), &mut try(cache_parser),
&mut try(open_parser), &mut try(open_parser),
@ -564,6 +581,16 @@ mod tests {
assert_eq!(err.to_string(), format!("Invalid command {:?}", input)); 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] #[test]
fn test_transact_parser_complete_edn() { fn test_transact_parser_complete_edn() {
let input = ".t [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; let input = ".t [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]";

View file

@ -53,6 +53,7 @@ pub fn run() -> i32 {
opts.optflag("h", "help", "Print this help message and exit"); 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("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("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"); opts.optflag("v", "version", "Print version and exit");
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
@ -84,6 +85,10 @@ pub fn run() -> i32 {
last_arg = None; last_arg = None;
Some(command_parser::Command::Query(arg.clone())) Some(command_parser::Command::Query(arg.clone()))
}, },
Some("-i") => {
last_arg = None;
Some(command_parser::Command::Import(arg.clone()))
},
Some("-t") => { Some("-t") => {
last_arg = None; last_arg = None;
Some(command_parser::Command::Transact(arg.clone())) Some(command_parser::Command::Transact(arg.clone()))

View file

@ -8,7 +8,7 @@
// 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::BTreeMap;
use std::io::Write; use std::io::Write;
use std::process; use std::process;
@ -46,6 +46,7 @@ use command_parser::{
SHORT_QUERY_COMMAND, SHORT_QUERY_COMMAND,
SCHEMA_COMMAND, SCHEMA_COMMAND,
SYNC_COMMAND, SYNC_COMMAND,
LONG_IMPORT_COMMAND,
LONG_TRANSACT_COMMAND, LONG_TRANSACT_COMMAND,
SHORT_TRANSACT_COMMAND, SHORT_TRANSACT_COMMAND,
LONG_EXIT_COMMAND, LONG_EXIT_COMMAND,
@ -63,8 +64,8 @@ use input::InputResult::{
}; };
lazy_static! { lazy_static! {
static ref COMMAND_HELP: HashMap<&'static str, &'static str> = { static ref COMMAND_HELP: BTreeMap<&'static str, &'static str> = {
let mut map = HashMap::new(); let mut map = BTreeMap::new();
map.insert(LONG_EXIT_COMMAND, "Close the current database and exit the REPL."); 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(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.");
@ -76,6 +77,7 @@ lazy_static! {
map.insert(SYNC_COMMAND, "Synchronize database against a Sync Server URL for a provided user UUID."); 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(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(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(LONG_QUERY_EXPLAIN_COMMAND, "Show the SQL and query plan that would be executed for a given query.");
map.insert(SHORT_QUERY_EXPLAIN_COMMAND, map.insert(SHORT_QUERY_EXPLAIN_COMMAND,
"Shortcut for `.explain_query`. Show the SQL and query plan that would be executed for a given query."); "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()), Err(e) => eprintln!("{}", e.to_string()),
}; };
}, },
Command::Import(path) => self.execute_import(path),
Command::Sync(args) => { Command::Sync(args) => {
match self.store.sync(&args[0], &args[1]) { match self.store.sync(&args[0], &args[1]) {
Ok(_) => println!("Synced!"), Ok(_) => println!("Synced!"),
@ -249,6 +252,17 @@ impl Repl {
} }
} }
fn execute_import<T>(&mut self, path: T)
where T: Into<String> {
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<T>(&mut self, path: T) -> ::mentat::errors::Result<()> fn open<T>(&mut self, path: T) -> ::mentat::errors::Result<()>
where T: Into<String> { where T: Into<String> {
let path = path.into(); let path = path.into();