From 30bf827d1604ff2ef2495f63bab7bdd4672d6f5f Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Mon, 5 Mar 2018 12:52:20 -0800 Subject: [PATCH] CLI improvements (#577) r=grisha * Add a prepared query command to CLI. * Print nanoseconds in the REPL. This is a good problem to have. * Better CLI timing. * Use release for 'cargo cli', debug for 'cargo debugcli'. * Don't enable debug symbols in release builds. * Clean up CLI code. Fixed order for help. * Column-align help output. --- .cargo/config | 3 +- Cargo.toml | 2 +- tools/cli/src/mentat_cli/command_parser.rs | 302 +++++++++++---------- tools/cli/src/mentat_cli/input.rs | 4 + tools/cli/src/mentat_cli/repl.rs | 224 +++++++++------ 5 files changed, 320 insertions(+), 215 deletions(-) diff --git a/.cargo/config b/.cargo/config index 2df10bcc..29a01abd 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,3 @@ [alias] -cli = ["run", "-p", "mentat_cli"] +cli = ["run", "--release", "-p", "mentat_cli"] +debugcli = ["run", "-p", "mentat_cli"] diff --git a/Cargo.toml b/Cargo.toml index e514780f..9a6a770b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,4 +80,4 @@ path = "tx-parser" path = "tolstoy" [profile.release] -debug = true +debug = false diff --git a/tools/cli/src/mentat_cli/command_parser.rs b/tools/cli/src/mentat_cli/command_parser.rs index f542703c..9eaa40a0 100644 --- a/tools/cli/src/mentat_cli/command_parser.rs +++ b/tools/cli/src/mentat_cli/command_parser.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. use combine::{ + Parser, any, eof, look_ahead, @@ -16,58 +17,63 @@ use combine::{ satisfy, sep_end_by, token, - Parser }; + use combine::char::{ space, spaces, - string + string, }; + use combine::combinator::{ choice, - try + try, }; use errors as cli; use edn; -use mentat::CacheDirection; +use mentat::{ + CacheDirection, +}; -pub static HELP_COMMAND: &'static str = &"help"; -pub static OPEN_COMMAND: &'static str = &"open"; -pub static OPEN_EMPTY_COMMAND: &'static str = &"empty"; -pub static CACHE_COMMAND: &'static str = &"cache"; -pub static CLOSE_COMMAND: &'static str = &"close"; -pub static LONG_QUERY_COMMAND: &'static str = &"query"; -pub static SHORT_QUERY_COMMAND: &'static str = &"q"; -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"; -pub static SHORT_QUERY_EXPLAIN_COMMAND: &'static str = &"eq"; -pub static SYNC_COMMAND: &'static str = &"sync"; +pub static COMMAND_CACHE: &'static str = &"cache"; +pub static COMMAND_CLOSE: &'static str = &"close"; +pub static COMMAND_EXIT_LONG: &'static str = &"exit"; +pub static COMMAND_EXIT_SHORT: &'static str = &"e"; +pub static COMMAND_HELP: &'static str = &"help"; +pub static COMMAND_IMPORT_LONG: &'static str = &"import"; +pub static COMMAND_IMPORT_SHORT: &'static str = &"i"; +pub static COMMAND_OPEN: &'static str = &"open"; +pub static COMMAND_OPEN_EMPTY: &'static str = &"empty"; +pub static COMMAND_QUERY_LONG: &'static str = &"query"; +pub static COMMAND_QUERY_SHORT: &'static str = &"q"; +pub static COMMAND_QUERY_EXPLAIN_LONG: &'static str = &"explain_query"; +pub static COMMAND_QUERY_EXPLAIN_SHORT: &'static str = &"eq"; +pub static COMMAND_QUERY_PREPARED_LONG: &'static str = &"query_prepared"; +pub static COMMAND_SCHEMA: &'static str = &"schema"; +pub static COMMAND_SYNC: &'static str = &"sync"; +pub static COMMAND_TIMER_LONG: &'static str = &"timer"; +pub static COMMAND_TRANSACT_LONG: &'static str = &"transact"; +pub static COMMAND_TRANSACT_SHORT: &'static str = &"t"; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Command { + Cache(String, CacheDirection), Close, Exit, Help(Vec), + Import(String), Open(String), OpenEmpty(String), - Cache(String, CacheDirection), Query(String), + QueryExplain(String), + QueryPrepared(String), Schema, Sync(Vec), Timer(bool), Transact(String), - Import(String), - QueryExplain(String), } impl Command { @@ -78,81 +84,91 @@ impl Command { pub fn is_complete(&self) -> bool { match self { &Command::Query(ref args) | - &Command::Transact(ref args) | - &Command::QueryExplain(ref args) => { + &Command::QueryExplain(ref args) | + &Command::QueryPrepared(ref args) | + &Command::Transact(ref args) + => { edn::parse::value(&args).is_ok() }, - &Command::Timer(_) | - &Command::Help(_) | - &Command::Open(_) | - &Command::OpenEmpty(_) | + &Command::Cache(_, _) | &Command::Close | &Command::Exit | + &Command::Help(_) | &Command::Import(_) | - &Command::Sync(_) | - &Command::Cache(_, _) | - &Command::Schema => true + &Command::Open(_) | + &Command::OpenEmpty(_) | + &Command::Timer(_) | + &Command::Schema | + &Command::Sync(_) + => true, } } pub fn is_timed(&self) -> bool { match self { - &Command::Query(_) | &Command::Import(_) | - &Command::Transact(_) => true, - &Command::QueryExplain(_) | - &Command::Timer(_) | - &Command::Help(_) | - &Command::Open(_) | - &Command::OpenEmpty(_) | + &Command::Query(_) | + &Command::QueryPrepared(_) | + &Command::Transact(_) + => true, + &Command::Cache(_, _) | &Command::Close | &Command::Exit | - &Command::Sync(_) | - &Command::Schema => false + &Command::Help(_) | + &Command::Open(_) | + &Command::OpenEmpty(_) | + &Command::QueryExplain(_) | + &Command::Timer(_) | + &Command::Schema | + &Command::Sync(_) + => false, } } pub fn output(&self) -> String { match self { - &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) - }, &Command::Cache(ref attr, ref direction) => { - format!(".{} {} {:?}", CACHE_COMMAND, attr, direction) - }, - &Command::Timer(on) => { - format!(".{} {}", LONG_TIMER_COMMAND, on) - }, - &Command::Help(ref args) => { - format!(".{} {:?}", HELP_COMMAND, args) - }, - &Command::Open(ref args) => { - format!(".{} {}", OPEN_COMMAND, args) - }, - &Command::OpenEmpty(ref args) => { - format!(".{} {}", OPEN_EMPTY_COMMAND, args) + format!(".{} {} {:?}", COMMAND_CACHE, attr, direction) }, &Command::Close => { - format!(".{}", CLOSE_COMMAND) + format!(".{}", COMMAND_CLOSE) }, &Command::Exit => { - format!(".{}", LONG_EXIT_COMMAND) + format!(".{}", COMMAND_EXIT_LONG) }, - &Command::Schema => { - format!(".{}", SCHEMA_COMMAND) + &Command::Help(ref args) => { + format!(".{} {:?}", COMMAND_HELP, args) }, - &Command::Sync(ref args) => { - format!(".{} {:?}", SYNC_COMMAND, args) + &Command::Import(ref args) => { + format!(".{} {}", COMMAND_IMPORT_LONG, args) + }, + &Command::Open(ref args) => { + format!(".{} {}", COMMAND_OPEN, args) + }, + &Command::OpenEmpty(ref args) => { + format!(".{} {}", COMMAND_OPEN_EMPTY, args) + }, + &Command::Query(ref args) => { + format!(".{} {}", COMMAND_QUERY_LONG, args) }, &Command::QueryExplain(ref args) => { - format!(".{} {}", LONG_QUERY_EXPLAIN_COMMAND, args) + format!(".{} {}", COMMAND_QUERY_EXPLAIN_LONG, args) + }, + &Command::QueryPrepared(ref args) => { + format!(".{} {}", COMMAND_QUERY_PREPARED_LONG, args) + }, + &Command::Schema => { + format!(".{}", COMMAND_SCHEMA) + }, + &Command::Sync(ref args) => { + format!(".{} {:?}", COMMAND_SYNC, args) + }, + &Command::Timer(on) => { + format!(".{} {}", COMMAND_TIMER_LONG, on) + }, + &Command::Transact(ref args) => { + format!(".{} {}", COMMAND_TRANSACT_LONG, args) }, } } @@ -163,33 +179,74 @@ pub fn command(s: &str) -> Result { 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"); - let help_parser = string(HELP_COMMAND) - .with(spaces()) - .with(arguments()) - .map(|args| { - Ok(Command::Help(args.clone())) - }); - - let timer_parser = string(LONG_TIMER_COMMAND) - .with(spaces()) - .with(string("on").map(|_| true).or(string("off").map(|_| false))) - .map(|args| { - Ok(Command::Timer(args)) - }); - + // Helpers. let direction_parser = || string("forward") .map(|_| CacheDirection::Forward) .or(string("reverse").map(|_| CacheDirection::Reverse)) .or(string("both").map(|_| CacheDirection::Both)); - let cache_parser = string(CACHE_COMMAND) + let edn_arg_parser = || spaces() + .with(look_ahead(string("[").or(string("{"))) + .with(many1::, _>(try(any()))) + .and_then(|args| -> Result { + Ok(args.iter().collect()) + }) + ); + + let no_arg_parser = || arguments() + .skip(spaces()) + .skip(eof()); + + + // Commands. + let cache_parser = string(COMMAND_CACHE) .with(spaces()) .with(argument().skip(spaces()).and(direction_parser()) .map(|(arg, direction)| { Ok(Command::Cache(arg, direction)) })); - let open_parser = string(OPEN_COMMAND) + + let close_parser = string(COMMAND_CLOSE) + .with(no_arg_parser()) + .map(|args| { + if !args.is_empty() { + bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); + } + Ok(Command::Close) + }); + + let exit_parser = try(string(COMMAND_EXIT_LONG)).or(try(string(COMMAND_EXIT_SHORT))) + .with(no_arg_parser()) + .map(|args| { + if !args.is_empty() { + bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); + } + Ok(Command::Exit) + }); + + let explain_query_parser = try(string(COMMAND_QUERY_EXPLAIN_LONG)) + .or(try(string(COMMAND_QUERY_EXPLAIN_SHORT))) + .with(edn_arg_parser()) + .map(|x| { + Ok(Command::QueryExplain(x)) + }); + + let help_parser = string(COMMAND_HELP) + .with(spaces()) + .with(arguments()) + .map(|args| { + Ok(Command::Help(args.clone())) + }); + + let import_parser = try(string(COMMAND_IMPORT_LONG)).or(try(string(COMMAND_IMPORT_SHORT))) + .with(spaces()) + .with(path()) + .map(|x| { + Ok(Command::Import(x)) + }); + + let open_parser = string(COMMAND_OPEN) .with(spaces()) .with(arguments()) .map(|args| { @@ -202,7 +259,7 @@ pub fn command(s: &str) -> Result { Ok(Command::Open(args[0].clone())) }); - let open_empty_parser = string(OPEN_EMPTY_COMMAND) + let open_empty_parser = string(COMMAND_OPEN_EMPTY) .with(spaces()) .with(arguments()) .map(|args| { @@ -215,20 +272,19 @@ pub fn command(s: &str) -> Result { Ok(Command::OpenEmpty(args[0].clone())) }); - let no_arg_parser = || arguments() - .skip(spaces()) - .skip(eof()); + let query_parser = try(string(COMMAND_QUERY_LONG)).or(try(string(COMMAND_QUERY_SHORT))) + .with(edn_arg_parser()) + .map(|x| { + Ok(Command::Query(x)) + }); - let close_parser = string(CLOSE_COMMAND) - .with(no_arg_parser()) - .map(|args| { - if !args.is_empty() { - bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) ); - } - Ok(Command::Close) - }); + let query_prepared_parser = string(COMMAND_QUERY_PREPARED_LONG) + .with(edn_arg_parser()) + .map(|x| { + Ok(Command::QueryPrepared(x)) + }); - let schema_parser = string(SCHEMA_COMMAND) + let schema_parser = string(COMMAND_SCHEMA) .with(no_arg_parser()) .map(|args| { if !args.is_empty() { @@ -237,7 +293,7 @@ pub fn command(s: &str) -> Result { Ok(Command::Schema) }); - let sync_parser = string(SYNC_COMMAND) + let sync_parser = string(COMMAND_SYNC) .with(spaces()) .with(arguments()) .map(|args| { @@ -250,51 +306,22 @@ pub fn command(s: &str) -> Result { Ok(Command::Sync(args.clone())) }); - 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()))) - .and_then(|args| -> Result { - Ok(args.iter().collect()) - }) - ); - - let query_parser = try(string(LONG_QUERY_COMMAND)).or(try(string(SHORT_QUERY_COMMAND))) - .with(edn_arg_parser()) - .map(|x| { - Ok(Command::Query(x)) - }); - - let import_parser = try(string(LONG_IMPORT_COMMAND)).or(try(string(SHORT_IMPORT_COMMAND))) + let timer_parser = string(COMMAND_TIMER_LONG) .with(spaces()) - .with(path()) - .map(|x| { - Ok(Command::Import(x)) + .with(string("on").map(|_| true).or(string("off").map(|_| false))) + .map(|args| { + Ok(Command::Timer(args)) }); - let transact_parser = try(string(LONG_TRANSACT_COMMAND)).or(try(string(SHORT_TRANSACT_COMMAND))) + let transact_parser = try(string(COMMAND_TRANSACT_LONG)).or(try(string(COMMAND_TRANSACT_SHORT))) .with(edn_arg_parser()) .map(|x| { Ok(Command::Transact(x)) }); - let explain_query_parser = try(string(LONG_QUERY_EXPLAIN_COMMAND)) - .or(try(string(SHORT_QUERY_EXPLAIN_COMMAND))) - .with(edn_arg_parser()) - .map(|x| { - Ok(Command::QueryExplain(x)) - }); spaces() .skip(token('.')) - .with(choice::<[&mut Parser>; 13], _> + .with(choice::<[&mut Parser>; 14], _> ([&mut try(help_parser), &mut try(import_parser), &mut try(timer_parser), @@ -304,6 +331,7 @@ pub fn command(s: &str) -> Result { &mut try(close_parser), &mut try(explain_query_parser), &mut try(exit_parser), + &mut try(query_prepared_parser), &mut try(query_parser), &mut try(schema_parser), &mut try(sync_parser), diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs index dd08bdf8..007511ce 100644 --- a/tools/cli/src/mentat_cli/input.rs +++ b/tools/cli/src/mentat_cli/input.rs @@ -132,6 +132,9 @@ impl InputReader { // Therefore, we add the newly read in line to the existing command args. // If there is no in process command, we parse the read in line as a new command. let cmd = match &self.in_process_cmd { + &Some(Command::QueryPrepared(ref args)) => { + Ok(Command::QueryPrepared(args.clone() + " " + &line)) + }, &Some(Command::Query(ref args)) => { Ok(Command::Query(args.clone() + " " + &line)) }, @@ -147,6 +150,7 @@ impl InputReader { Ok(cmd) => { match cmd { Command::Query(_) | + Command::QueryPrepared(_) | Command::Transact(_) | Command::QueryExplain(_) if !cmd.is_complete() => { // A query or transact is complete if it contains a valid EDN. diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index 4fac74ec..06fa44c2 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -8,7 +8,6 @@ // 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::BTreeMap; use std::io::Write; use std::process; @@ -39,49 +38,62 @@ use mentat::{ use command_parser::{ Command, - HELP_COMMAND, - OPEN_COMMAND, - CACHE_COMMAND, - LONG_QUERY_COMMAND, - SHORT_QUERY_COMMAND, - SCHEMA_COMMAND, - SYNC_COMMAND, - LONG_IMPORT_COMMAND, - LONG_TRANSACT_COMMAND, - SHORT_TRANSACT_COMMAND, - LONG_EXIT_COMMAND, - SHORT_EXIT_COMMAND, - LONG_QUERY_EXPLAIN_COMMAND, - SHORT_QUERY_EXPLAIN_COMMAND, +}; + +use command_parser::{ + COMMAND_CACHE, + COMMAND_EXIT_LONG, + COMMAND_EXIT_SHORT, + COMMAND_HELP, + COMMAND_IMPORT_LONG, + COMMAND_OPEN, + COMMAND_QUERY_LONG, + COMMAND_QUERY_SHORT, + COMMAND_QUERY_EXPLAIN_LONG, + COMMAND_QUERY_EXPLAIN_SHORT, + COMMAND_QUERY_PREPARED_LONG, + COMMAND_SCHEMA, + COMMAND_SYNC, + COMMAND_TRANSACT_LONG, + COMMAND_TRANSACT_SHORT, }; use input::InputReader; use input::InputResult::{ - MetaCommand, Empty, - More, Eof, + MetaCommand, + More, }; lazy_static! { - 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."); - map.insert(OPEN_COMMAND, "Open a database at path."); - map.insert(CACHE_COMMAND, "Cache an attribute. Usage: `.cache :foo/bar reverse`"); - map.insert(LONG_QUERY_COMMAND, "Execute a query against the current open database."); - map.insert(SHORT_QUERY_COMMAND, "Shortcut for `.query`. Execute a query against the current open database."); - map.insert(SCHEMA_COMMAND, "Output the schema for the current open database."); - 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."); - map + static ref HELP_COMMANDS: Vec<(&'static str, &'static str)> = { + vec![ + (COMMAND_HELP, "Show this message."), + + (COMMAND_EXIT_LONG, "Close the current database and exit the REPL."), + (COMMAND_EXIT_SHORT, "Shortcut for `.exit`. Close the current database and exit the REPL."), + + (COMMAND_OPEN, "Open a database at path."), + + (COMMAND_SCHEMA, "Output the schema for the current open database."), + + (COMMAND_IMPORT_LONG, "Transact the contents of a file against the current open database."), + + (COMMAND_QUERY_LONG, "Execute a query against the current open database."), + (COMMAND_QUERY_SHORT, "Shortcut for `.query`. Execute a query against the current open database."), + + (COMMAND_QUERY_PREPARED_LONG, "Prepare a query against the current open database, then run it, timed."), + + (COMMAND_TRANSACT_LONG, "Execute a transact against the current open database."), + (COMMAND_TRANSACT_SHORT, "Shortcut for `.transact`. Execute a transact against the current open database."), + + (COMMAND_QUERY_EXPLAIN_LONG, "Show the SQL and query plan that would be executed for a given query."), + (COMMAND_QUERY_EXPLAIN_SHORT, "Shortcut for `.explain_query`. Show the SQL and query plan that would be executed for a given query."), + + (COMMAND_CACHE, "Cache an attribute. Usage: `.cache :foo/bar reverse`"), + (COMMAND_SYNC, "Synchronize the database against a Sync Server URL for a provided user UUID."), + ] }; } @@ -101,12 +113,25 @@ fn parse_namespaced_keyword(input: &str) -> Option { } fn format_time(duration: Duration) { + let m_nanos = duration.num_nanoseconds(); + if let Some(nanos) = m_nanos { + if nanos < 1_000 { + eprintln!("{bold}{nanos}{reset}ns", + bold = style::Bold, + nanos = nanos, + reset = style::Reset); + return; + } + } + let m_micros = duration.num_microseconds(); if let Some(micros) = m_micros { if micros < 10_000 { - eprintln!("{bold}{micros}{reset}µs", + let ns = m_nanos.unwrap_or(0) / 1000; + eprintln!("{bold}{micros}.{ns}{reset}µs", bold = style::Bold, micros = micros, + ns = ns, reset = style::Reset); return; } @@ -146,7 +171,7 @@ impl Repl { /// Constructs a new `Repl`. pub fn new() -> Result { let store = Store::open("").map_err(|e| e.to_string())?; - Ok(Repl{ + Ok(Repl { path: "".to_string(), store: store, timer_on: false, @@ -176,7 +201,7 @@ impl Repl { Ok(More) => (), Ok(Eof) => { if input.is_tty() { - println!(""); + println!(); } break; }, @@ -198,54 +223,102 @@ impl Repl { /// Runs a single command input. fn handle_command(&mut self, cmd: Command) { - let should_time = self.timer_on && cmd.is_timed(); + let should_print_times = self.timer_on && cmd.is_timed(); - let start = PreciseTime::now(); + let mut start = PreciseTime::now(); + let mut end: Option = None; match cmd { - Command::Help(args) => self.help_command(args), - Command::Timer(on) => self.toggle_timer(on), - Command::Cache(attr, direction) => self.cache(attr, direction), + Command::Cache(attr, direction) => { + self.cache(attr, direction); + }, + Command::Close => { + self.close(); + }, + Command::Exit => { + self.close(); + eprintln!("Exiting…"); + process::exit(0); + }, + Command::Help(args) => { + self.help_command(args); + }, + Command::Import(path) => { + self.execute_import(path); + }, Command::Open(db) => { match self.open(db) { Ok(_) => println!("Database {:?} opened", self.db_name()), 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!"), - Err(e) => eprintln!("{:?}", e) - }; - } Command::OpenEmpty(db) => { match self.open_empty(db) { Ok(_) => println!("Empty database {:?} opened", self.db_name()), Err(e) => eprintln!("{}", e.to_string()), }; }, - Command::Close => self.close(), - Command::Query(query) => self.execute_query(query), - Command::QueryExplain(query) => self.explain_query(query), + Command::Query(query) => { + self.store + .q_once(query.as_str(), None) + .map_err(|e| e.into()) + .and_then(|o| { + end = Some(PreciseTime::now()); + self.print_results(o) + }) + .map_err(|err| { + eprintln!("{:?}.", err); + }) + .ok(); + }, + Command::QueryExplain(query) => { + self.explain_query(query); + }, + Command::QueryPrepared(query) => { + self.store + .q_prepare(query.as_str(), None) + .and_then(|mut p| { + let prepare_end = PreciseTime::now(); + if should_print_times { + eprint_out("Prepare time"); + eprint!(": "); + format_time(start.to(prepare_end)); + } + // This is a hack. + start = PreciseTime::now(); + let r = p.run(None); + end = Some(PreciseTime::now()); + return r; + }) + .map(|o| self.print_results(o)) + .map_err(|err| { + eprintln!("{:?}.", err); + }) + .ok(); + }, Command::Schema => { let edn = self.store.conn().current_schema().to_edn_value(); match edn.to_pretty(120) { Ok(s) => println!("{}", s), Err(e) => eprintln!("{}", e) }; - - } - Command::Transact(transaction) => self.execute_transact(transaction), - Command::Exit => { - self.close(); - eprintln!("Exiting..."); - process::exit(0); + }, + Command::Sync(args) => { + match self.store.sync(&args[0], &args[1]) { + Ok(_) => println!("Synced!"), + Err(e) => eprintln!("{:?}", e) + }; } + Command::Timer(on) => { + self.toggle_timer(on); + }, + Command::Transact(transaction) => { + self.execute_transact(transaction); + }, } - let end = PreciseTime::now(); - if should_time { + let end = end.unwrap_or_else(PreciseTime::now); + if should_print_times { eprint_out("Run time"); eprint!(": "); format_time(start.to(end)); @@ -301,32 +374,31 @@ impl Repl { } fn help_command(&self, args: Vec) { + let stdout = ::std::io::stdout(); + let mut output = TabWriter::new(stdout.lock()); if args.is_empty() { - for (cmd, msg) in COMMAND_HELP.iter() { - println!(".{} - {}", cmd, msg); + for &(cmd, msg) in HELP_COMMANDS.iter() { + write!(output, ".{}\t", cmd).unwrap(); + writeln!(output, "{}", msg).unwrap(); } } else { for mut arg in args { if arg.chars().nth(0).unwrap() == '.' { arg.remove(0); } - let msg = COMMAND_HELP.get(arg.as_str()); - if msg.is_some() { - println!(".{} - {}", arg, msg.unwrap()); + if let Some(&(cmd, msg)) = HELP_COMMANDS.iter() + .filter(|&&(c, _)| c == arg.as_str()) + .next() { + write!(output, ".{}\t", cmd).unwrap(); + writeln!(output, "{}", msg).unwrap(); } else { eprintln!("Unrecognised command {}", arg); + return; } } } - } - - pub fn execute_query(&self, query: String) { - self.store.q_once(query.as_str(), None) - .map_err(|e| e.into()) - .and_then(|o| self.print_results(o)) - .map_err(|err| { - eprintln!("{:?}.", err); - }).ok(); + writeln!(output, "").unwrap(); + output.flush().unwrap(); } fn print_results(&self, query_output: QueryOutput) -> Result<(), ::errors::Error> {