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:
parent
d46535a7c2
commit
30bf827d16
5 changed files with 320 additions and 215 deletions
|
@ -1,2 +1,3 @@
|
|||
[alias]
|
||||
cli = ["run", "-p", "mentat_cli"]
|
||||
cli = ["run", "--release", "-p", "mentat_cli"]
|
||||
debugcli = ["run", "-p", "mentat_cli"]
|
||||
|
|
|
@ -80,4 +80,4 @@ path = "tx-parser"
|
|||
path = "tolstoy"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
debug = false
|
||||
|
|
|
@ -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<String>),
|
||||
Import(String),
|
||||
Open(String),
|
||||
OpenEmpty(String),
|
||||
Cache(String, CacheDirection),
|
||||
Query(String),
|
||||
QueryExplain(String),
|
||||
QueryPrepared(String),
|
||||
Schema,
|
||||
Sync(Vec<String>),
|
||||
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<Command, cli::Error> {
|
|||
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 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::<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(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<Command, cli::Error> {
|
|||
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<Command, cli::Error> {
|
|||
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<Command, cli::Error> {
|
|||
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<Command, cli::Error> {
|
|||
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::<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)))
|
||||
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<Input = _, Output = Result<Command, cli::Error>>; 13], _>
|
||||
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 14], _>
|
||||
([&mut try(help_parser),
|
||||
&mut try(import_parser),
|
||||
&mut try(timer_parser),
|
||||
|
@ -304,6 +331,7 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
|
|||
&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),
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<NamespacedKeyword> {
|
|||
}
|
||||
|
||||
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<Repl, String> {
|
||||
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<PreciseTime> = 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<String>) {
|
||||
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> {
|
||||
|
|
Loading…
Reference in a new issue