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]
|
[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"
|
path = "tolstoy"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = false
|
||||||
|
|
|
@ -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)
|
let query_prepared_parser = string(COMMAND_QUERY_PREPARED_LONG)
|
||||||
.with(no_arg_parser())
|
.with(edn_arg_parser())
|
||||||
.map(|args| {
|
.map(|x| {
|
||||||
if !args.is_empty() {
|
Ok(Command::QueryPrepared(x))
|
||||||
bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) );
|
});
|
||||||
}
|
|
||||||
Ok(Command::Close)
|
|
||||||
});
|
|
||||||
|
|
||||||
let schema_parser = string(SCHEMA_COMMAND)
|
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),
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue