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.
This commit is contained in:
Richard Newman 2018-03-05 12:52:20 -08:00 committed by GitHub
parent d46535a7c2
commit 30bf827d16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 320 additions and 215 deletions

View file

@ -1,2 +1,3 @@
[alias] [alias]
cli = ["run", "-p", "mentat_cli"] cli = ["run", "--release", "-p", "mentat_cli"]
debugcli = ["run", "-p", "mentat_cli"]

View file

@ -80,4 +80,4 @@ path = "tx-parser"
path = "tolstoy" path = "tolstoy"
[profile.release] [profile.release]
debug = true debug = false

View file

@ -9,6 +9,7 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use combine::{ use combine::{
Parser,
any, any,
eof, eof,
look_ahead, look_ahead,
@ -16,58 +17,63 @@ use combine::{
satisfy, satisfy,
sep_end_by, sep_end_by,
token, token,
Parser
}; };
use combine::char::{ use combine::char::{
space, space,
spaces, spaces,
string string,
}; };
use combine::combinator::{ use combine::combinator::{
choice, choice,
try try,
}; };
use errors as cli; use errors as cli;
use edn; use edn;
use mentat::CacheDirection; use mentat::{
CacheDirection,
};
pub static HELP_COMMAND: &'static str = &"help"; pub static COMMAND_CACHE: &'static str = &"cache";
pub static OPEN_COMMAND: &'static str = &"open"; pub static COMMAND_CLOSE: &'static str = &"close";
pub static OPEN_EMPTY_COMMAND: &'static str = &"empty"; pub static COMMAND_EXIT_LONG: &'static str = &"exit";
pub static CACHE_COMMAND: &'static str = &"cache"; pub static COMMAND_EXIT_SHORT: &'static str = &"e";
pub static CLOSE_COMMAND: &'static str = &"close"; pub static COMMAND_HELP: &'static str = &"help";
pub static LONG_QUERY_COMMAND: &'static str = &"query"; pub static COMMAND_IMPORT_LONG: &'static str = &"import";
pub static SHORT_QUERY_COMMAND: &'static str = &"q"; pub static COMMAND_IMPORT_SHORT: &'static str = &"i";
pub static SCHEMA_COMMAND: &'static str = &"schema"; pub static COMMAND_OPEN: &'static str = &"open";
pub static LONG_TIMER_COMMAND: &'static str = &"timer"; pub static COMMAND_OPEN_EMPTY: &'static str = &"empty";
pub static LONG_TRANSACT_COMMAND: &'static str = &"transact"; pub static COMMAND_QUERY_LONG: &'static str = &"query";
pub static SHORT_TRANSACT_COMMAND: &'static str = &"t"; pub static COMMAND_QUERY_SHORT: &'static str = &"q";
pub static LONG_IMPORT_COMMAND: &'static str = &"import"; pub static COMMAND_QUERY_EXPLAIN_LONG: &'static str = &"explain_query";
pub static SHORT_IMPORT_COMMAND: &'static str = &"i"; pub static COMMAND_QUERY_EXPLAIN_SHORT: &'static str = &"eq";
pub static LONG_EXIT_COMMAND: &'static str = &"exit"; pub static COMMAND_QUERY_PREPARED_LONG: &'static str = &"query_prepared";
pub static SHORT_EXIT_COMMAND: &'static str = &"e"; pub static COMMAND_SCHEMA: &'static str = &"schema";
pub static LONG_QUERY_EXPLAIN_COMMAND: &'static str = &"explain_query"; pub static COMMAND_SYNC: &'static str = &"sync";
pub static SHORT_QUERY_EXPLAIN_COMMAND: &'static str = &"eq"; pub static COMMAND_TIMER_LONG: &'static str = &"timer";
pub static SYNC_COMMAND: &'static str = &"sync"; pub static COMMAND_TRANSACT_LONG: &'static str = &"transact";
pub static COMMAND_TRANSACT_SHORT: &'static str = &"t";
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Command { pub enum Command {
Cache(String, CacheDirection),
Close, Close,
Exit, Exit,
Help(Vec<String>), Help(Vec<String>),
Import(String),
Open(String), Open(String),
OpenEmpty(String), OpenEmpty(String),
Cache(String, CacheDirection),
Query(String), Query(String),
QueryExplain(String),
QueryPrepared(String),
Schema, Schema,
Sync(Vec<String>), Sync(Vec<String>),
Timer(bool), Timer(bool),
Transact(String), Transact(String),
Import(String),
QueryExplain(String),
} }
impl Command { impl Command {
@ -78,81 +84,91 @@ impl Command {
pub fn is_complete(&self) -> bool { pub fn is_complete(&self) -> bool {
match self { match self {
&Command::Query(ref args) | &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() edn::parse::value(&args).is_ok()
}, },
&Command::Timer(_) | &Command::Cache(_, _) |
&Command::Help(_) |
&Command::Open(_) |
&Command::OpenEmpty(_) |
&Command::Close | &Command::Close |
&Command::Exit | &Command::Exit |
&Command::Help(_) |
&Command::Import(_) | &Command::Import(_) |
&Command::Sync(_) | &Command::Open(_) |
&Command::Cache(_, _) | &Command::OpenEmpty(_) |
&Command::Schema => true &Command::Timer(_) |
&Command::Schema |
&Command::Sync(_)
=> true,
} }
} }
pub fn is_timed(&self) -> bool { pub fn is_timed(&self) -> bool {
match self { match self {
&Command::Query(_) |
&Command::Import(_) | &Command::Import(_) |
&Command::Transact(_) => true, &Command::Query(_) |
&Command::QueryExplain(_) | &Command::QueryPrepared(_) |
&Command::Timer(_) | &Command::Transact(_)
&Command::Help(_) | => true,
&Command::Open(_) |
&Command::OpenEmpty(_) |
&Command::Cache(_, _) | &Command::Cache(_, _) |
&Command::Close | &Command::Close |
&Command::Exit | &Command::Exit |
&Command::Sync(_) | &Command::Help(_) |
&Command::Schema => false &Command::Open(_) |
&Command::OpenEmpty(_) |
&Command::QueryExplain(_) |
&Command::Timer(_) |
&Command::Schema |
&Command::Sync(_)
=> false,
} }
} }
pub fn output(&self) -> String { pub fn output(&self) -> String {
match self { 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) => { &Command::Cache(ref attr, ref direction) => {
format!(".{} {} {:?}", CACHE_COMMAND, attr, direction) format!(".{} {} {:?}", COMMAND_CACHE, 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)
}, },
&Command::Close => { &Command::Close => {
format!(".{}", CLOSE_COMMAND) format!(".{}", COMMAND_CLOSE)
}, },
&Command::Exit => { &Command::Exit => {
format!(".{}", LONG_EXIT_COMMAND) format!(".{}", COMMAND_EXIT_LONG)
}, },
&Command::Schema => { &Command::Help(ref args) => {
format!(".{}", SCHEMA_COMMAND) format!(".{} {:?}", COMMAND_HELP, args)
}, },
&Command::Sync(ref args) => { &Command::Import(ref args) => {
format!(".{} {:?}", SYNC_COMMAND, 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) => { &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<Command, cli::Error> {
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");
let help_parser = string(HELP_COMMAND) // Helpers.
.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))
});
let direction_parser = || string("forward") let direction_parser = || string("forward")
.map(|_| CacheDirection::Forward) .map(|_| CacheDirection::Forward)
.or(string("reverse").map(|_| CacheDirection::Reverse)) .or(string("reverse").map(|_| CacheDirection::Reverse))
.or(string("both").map(|_| CacheDirection::Both)); .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::<Vec<_>, _>(try(any())))
.and_then(|args| -> Result<String, cli::Error> {
Ok(args.iter().collect())
})
);
let no_arg_parser = || arguments()
.skip(spaces())
.skip(eof());
// Commands.
let cache_parser = string(COMMAND_CACHE)
.with(spaces()) .with(spaces())
.with(argument().skip(spaces()).and(direction_parser()) .with(argument().skip(spaces()).and(direction_parser())
.map(|(arg, direction)| { .map(|(arg, direction)| {
Ok(Command::Cache(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(spaces())
.with(arguments()) .with(arguments())
.map(|args| { .map(|args| {
@ -202,7 +259,7 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
Ok(Command::Open(args[0].clone())) 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(spaces())
.with(arguments()) .with(arguments())
.map(|args| { .map(|args| {
@ -215,20 +272,19 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
Ok(Command::OpenEmpty(args[0].clone())) Ok(Command::OpenEmpty(args[0].clone()))
}); });
let no_arg_parser = || arguments() let query_parser = try(string(COMMAND_QUERY_LONG)).or(try(string(COMMAND_QUERY_SHORT)))
.skip(spaces()) .with(edn_arg_parser())
.skip(eof()); .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 schema_parser = string(SCHEMA_COMMAND) let query_prepared_parser = string(COMMAND_QUERY_PREPARED_LONG)
.with(edn_arg_parser())
.map(|x| {
Ok(Command::QueryPrepared(x))
});
let schema_parser = string(COMMAND_SCHEMA)
.with(no_arg_parser()) .with(no_arg_parser())
.map(|args| { .map(|args| {
if !args.is_empty() { if !args.is_empty() {
@ -237,7 +293,7 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
Ok(Command::Schema) Ok(Command::Schema)
}); });
let sync_parser = string(SYNC_COMMAND) let sync_parser = string(COMMAND_SYNC)
.with(spaces()) .with(spaces())
.with(arguments()) .with(arguments())
.map(|args| { .map(|args| {
@ -250,51 +306,22 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
Ok(Command::Sync(args.clone())) Ok(Command::Sync(args.clone()))
}); });
let exit_parser = try(string(LONG_EXIT_COMMAND)).or(try(string(SHORT_EXIT_COMMAND))) let timer_parser = string(COMMAND_TIMER_LONG)
.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::<Vec<_>, _>(try(any())))
.and_then(|args| -> Result<String, cli::Error> {
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)))
.with(spaces()) .with(spaces())
.with(path()) .with(string("on").map(|_| true).or(string("off").map(|_| false)))
.map(|x| { .map(|args| {
Ok(Command::Import(x)) 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()) .with(edn_arg_parser())
.map(|x| { .map(|x| {
Ok(Command::Transact(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() spaces()
.skip(token('.')) .skip(token('.'))
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 13], _> .with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 14], _>
([&mut try(help_parser), ([&mut try(help_parser),
&mut try(import_parser), &mut try(import_parser),
&mut try(timer_parser), &mut try(timer_parser),
@ -304,6 +331,7 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
&mut try(close_parser), &mut try(close_parser),
&mut try(explain_query_parser), &mut try(explain_query_parser),
&mut try(exit_parser), &mut try(exit_parser),
&mut try(query_prepared_parser),
&mut try(query_parser), &mut try(query_parser),
&mut try(schema_parser), &mut try(schema_parser),
&mut try(sync_parser), &mut try(sync_parser),

View file

@ -132,6 +132,9 @@ impl InputReader {
// Therefore, we add the newly read in line to the existing command args. // 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. // If there is no in process command, we parse the read in line as a new command.
let cmd = match &self.in_process_cmd { let cmd = match &self.in_process_cmd {
&Some(Command::QueryPrepared(ref args)) => {
Ok(Command::QueryPrepared(args.clone() + " " + &line))
},
&Some(Command::Query(ref args)) => { &Some(Command::Query(ref args)) => {
Ok(Command::Query(args.clone() + " " + &line)) Ok(Command::Query(args.clone() + " " + &line))
}, },
@ -147,6 +150,7 @@ impl InputReader {
Ok(cmd) => { Ok(cmd) => {
match cmd { match cmd {
Command::Query(_) | Command::Query(_) |
Command::QueryPrepared(_) |
Command::Transact(_) | Command::Transact(_) |
Command::QueryExplain(_) if !cmd.is_complete() => { Command::QueryExplain(_) if !cmd.is_complete() => {
// A query or transact is complete if it contains a valid EDN. // A query or transact is complete if it contains a valid EDN.

View file

@ -8,7 +8,6 @@
// 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::BTreeMap;
use std::io::Write; use std::io::Write;
use std::process; use std::process;
@ -39,49 +38,62 @@ use mentat::{
use command_parser::{ use command_parser::{
Command, Command,
HELP_COMMAND, };
OPEN_COMMAND,
CACHE_COMMAND, use command_parser::{
LONG_QUERY_COMMAND, COMMAND_CACHE,
SHORT_QUERY_COMMAND, COMMAND_EXIT_LONG,
SCHEMA_COMMAND, COMMAND_EXIT_SHORT,
SYNC_COMMAND, COMMAND_HELP,
LONG_IMPORT_COMMAND, COMMAND_IMPORT_LONG,
LONG_TRANSACT_COMMAND, COMMAND_OPEN,
SHORT_TRANSACT_COMMAND, COMMAND_QUERY_LONG,
LONG_EXIT_COMMAND, COMMAND_QUERY_SHORT,
SHORT_EXIT_COMMAND, COMMAND_QUERY_EXPLAIN_LONG,
LONG_QUERY_EXPLAIN_COMMAND, COMMAND_QUERY_EXPLAIN_SHORT,
SHORT_QUERY_EXPLAIN_COMMAND, COMMAND_QUERY_PREPARED_LONG,
COMMAND_SCHEMA,
COMMAND_SYNC,
COMMAND_TRANSACT_LONG,
COMMAND_TRANSACT_SHORT,
}; };
use input::InputReader; use input::InputReader;
use input::InputResult::{ use input::InputResult::{
MetaCommand,
Empty, Empty,
More,
Eof, Eof,
MetaCommand,
More,
}; };
lazy_static! { lazy_static! {
static ref COMMAND_HELP: BTreeMap<&'static str, &'static str> = { static ref HELP_COMMANDS: Vec<(&'static str, &'static str)> = {
let mut map = BTreeMap::new(); vec![
map.insert(LONG_EXIT_COMMAND, "Close the current database and exit the REPL."); (COMMAND_HELP, "Show this message."),
map.insert(SHORT_EXIT_COMMAND, "Shortcut for `.exit`. Close the current database and exit the REPL.");
map.insert(HELP_COMMAND, "Show help for commands."); (COMMAND_EXIT_LONG, "Close the current database and exit the REPL."),
map.insert(OPEN_COMMAND, "Open a database at path."); (COMMAND_EXIT_SHORT, "Shortcut for `.exit`. Close the current database and exit the REPL."),
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."); (COMMAND_OPEN, "Open a database at path."),
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."); (COMMAND_SCHEMA, "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."); (COMMAND_IMPORT_LONG, "Transact the contents of a file 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."); (COMMAND_QUERY_LONG, "Execute a query 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."); (COMMAND_QUERY_SHORT, "Shortcut for `.query`. Execute a query against the current open database."),
map.insert(SHORT_QUERY_EXPLAIN_COMMAND,
"Shortcut for `.explain_query`. Show the SQL and query plan that would be executed for a given query."); (COMMAND_QUERY_PREPARED_LONG, "Prepare a query against the current open database, then run it, timed."),
map
(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<NamespacedKeyword> {
} }
fn format_time(duration: Duration) { 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(); let m_micros = duration.num_microseconds();
if let Some(micros) = m_micros { if let Some(micros) = m_micros {
if micros < 10_000 { 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, bold = style::Bold,
micros = micros, micros = micros,
ns = ns,
reset = style::Reset); reset = style::Reset);
return; return;
} }
@ -146,7 +171,7 @@ impl Repl {
/// Constructs a new `Repl`. /// Constructs a new `Repl`.
pub fn new() -> Result<Repl, String> { pub fn new() -> Result<Repl, String> {
let store = Store::open("").map_err(|e| e.to_string())?; let store = Store::open("").map_err(|e| e.to_string())?;
Ok(Repl{ Ok(Repl {
path: "".to_string(), path: "".to_string(),
store: store, store: store,
timer_on: false, timer_on: false,
@ -176,7 +201,7 @@ impl Repl {
Ok(More) => (), Ok(More) => (),
Ok(Eof) => { Ok(Eof) => {
if input.is_tty() { if input.is_tty() {
println!(""); println!();
} }
break; break;
}, },
@ -198,54 +223,102 @@ impl Repl {
/// Runs a single command input. /// Runs a single command input.
fn handle_command(&mut self, cmd: Command) { 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<PreciseTime> = None;
match cmd { match cmd {
Command::Help(args) => self.help_command(args), Command::Cache(attr, direction) => {
Command::Timer(on) => self.toggle_timer(on), 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) => { Command::Open(db) => {
match self.open(db) { match self.open(db) {
Ok(_) => println!("Database {:?} opened", self.db_name()), Ok(_) => println!("Database {:?} opened", self.db_name()),
Err(e) => eprintln!("{}", e.to_string()), 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) => { Command::OpenEmpty(db) => {
match self.open_empty(db) { match self.open_empty(db) {
Ok(_) => println!("Empty database {:?} opened", self.db_name()), Ok(_) => println!("Empty database {:?} opened", self.db_name()),
Err(e) => eprintln!("{}", e.to_string()), Err(e) => eprintln!("{}", e.to_string()),
}; };
}, },
Command::Close => self.close(), Command::Query(query) => {
Command::Query(query) => self.execute_query(query), self.store
Command::QueryExplain(query) => self.explain_query(query), .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 => { Command::Schema => {
let edn = self.store.conn().current_schema().to_edn_value(); let edn = self.store.conn().current_schema().to_edn_value();
match edn.to_pretty(120) { match edn.to_pretty(120) {
Ok(s) => println!("{}", s), Ok(s) => println!("{}", s),
Err(e) => eprintln!("{}", e) Err(e) => eprintln!("{}", e)
}; };
},
} Command::Sync(args) => {
Command::Transact(transaction) => self.execute_transact(transaction), match self.store.sync(&args[0], &args[1]) {
Command::Exit => { Ok(_) => println!("Synced!"),
self.close(); Err(e) => eprintln!("{:?}", e)
eprintln!("Exiting..."); };
process::exit(0);
} }
Command::Timer(on) => {
self.toggle_timer(on);
},
Command::Transact(transaction) => {
self.execute_transact(transaction);
},
} }
let end = PreciseTime::now(); let end = end.unwrap_or_else(PreciseTime::now);
if should_time { if should_print_times {
eprint_out("Run time"); eprint_out("Run time");
eprint!(": "); eprint!(": ");
format_time(start.to(end)); format_time(start.to(end));
@ -301,32 +374,31 @@ impl Repl {
} }
fn help_command(&self, args: Vec<String>) { fn help_command(&self, args: Vec<String>) {
let stdout = ::std::io::stdout();
let mut output = TabWriter::new(stdout.lock());
if args.is_empty() { if args.is_empty() {
for (cmd, msg) in COMMAND_HELP.iter() { for &(cmd, msg) in HELP_COMMANDS.iter() {
println!(".{} - {}", cmd, msg); write!(output, ".{}\t", cmd).unwrap();
writeln!(output, "{}", msg).unwrap();
} }
} else { } else {
for mut arg in args { for mut arg in args {
if arg.chars().nth(0).unwrap() == '.' { if arg.chars().nth(0).unwrap() == '.' {
arg.remove(0); arg.remove(0);
} }
let msg = COMMAND_HELP.get(arg.as_str()); if let Some(&(cmd, msg)) = HELP_COMMANDS.iter()
if msg.is_some() { .filter(|&&(c, _)| c == arg.as_str())
println!(".{} - {}", arg, msg.unwrap()); .next() {
write!(output, ".{}\t", cmd).unwrap();
writeln!(output, "{}", msg).unwrap();
} else { } else {
eprintln!("Unrecognised command {}", arg); eprintln!("Unrecognised command {}", arg);
return;
} }
} }
} }
} writeln!(output, "").unwrap();
output.flush().unwrap();
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();
} }
fn print_results(&self, query_output: QueryOutput) -> Result<(), ::errors::Error> { fn print_results(&self, query_output: QueryOutput) -> Result<(), ::errors::Error> {