Timing and colorizing in the CLI. (#552) r=grisha
* Add basic coloring of CLI output. * Add a timer to the CLI. Toggle it on or off with 'timer on' and 'timer off'. Output is colorized. * Add VSCode configuration files. These allow you to build and run the CLI, build Mentat, or run all tests.
This commit is contained in:
parent
dec86bb2c5
commit
0dcb7df1c7
7 changed files with 185 additions and 2 deletions
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "CLI",
|
||||
"stdio": "*",
|
||||
"program": "${workspaceFolder}/target/debug/mentat_cli",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"terminal": "integrated"
|
||||
},
|
||||
]
|
||||
}
|
58
.vscode/tasks.json
vendored
Normal file
58
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Test all",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"test",
|
||||
"--all",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Run CLI",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"cli",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "test"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Build CLI",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build",
|
||||
"-p",
|
||||
"mentat_cli",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"label": "Build Mentat",
|
||||
"command": "cargo",
|
||||
"args": [
|
||||
"build",
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -20,6 +20,8 @@ linefeed = "0.4"
|
|||
log = "0.3"
|
||||
tabwriter = "1"
|
||||
tempfile = "1.1"
|
||||
termion = "1"
|
||||
time = "0.1"
|
||||
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
|
||||
|
||||
[dependencies.rusqlite]
|
||||
|
|
|
@ -38,6 +38,7 @@ 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_EXIT_COMMAND: &'static str = &"exit";
|
||||
|
@ -53,6 +54,7 @@ pub enum Command {
|
|||
Open(String),
|
||||
Query(String),
|
||||
Schema,
|
||||
Timer(bool),
|
||||
Transact(String),
|
||||
QueryExplain(String),
|
||||
}
|
||||
|
@ -69,6 +71,7 @@ impl Command {
|
|||
&Command::QueryExplain(ref args) => {
|
||||
edn::parse::value(&args).is_ok()
|
||||
},
|
||||
&Command::Timer(_) |
|
||||
&Command::Help(_) |
|
||||
&Command::Open(_) |
|
||||
&Command::Close |
|
||||
|
@ -77,6 +80,20 @@ impl Command {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_timed(&self) -> bool {
|
||||
match self {
|
||||
&Command::Query(_) |
|
||||
&Command::Transact(_) => true,
|
||||
&Command::QueryExplain(_) |
|
||||
&Command::Timer(_) |
|
||||
&Command::Help(_) |
|
||||
&Command::Open(_) |
|
||||
&Command::Close |
|
||||
&Command::Exit |
|
||||
&Command::Schema => false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output(&self) -> String {
|
||||
match self {
|
||||
&Command::Query(ref args) => {
|
||||
|
@ -85,6 +102,9 @@ impl Command {
|
|||
&Command::Transact(ref args) => {
|
||||
format!(".{} {}", LONG_TRANSACT_COMMAND, args)
|
||||
},
|
||||
&Command::Timer(on) => {
|
||||
format!(".{} {}", LONG_TIMER_COMMAND, on)
|
||||
},
|
||||
&Command::Help(ref args) => {
|
||||
format!(".{} {:?}", HELP_COMMAND, args)
|
||||
},
|
||||
|
@ -117,6 +137,13 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
|
|||
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 open_parser = string(OPEN_COMMAND)
|
||||
.with(spaces())
|
||||
.with(arguments())
|
||||
|
@ -189,8 +216,9 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
|
|||
});
|
||||
spaces()
|
||||
.skip(token('.'))
|
||||
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 8], _>
|
||||
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 9], _>
|
||||
([&mut try(help_parser),
|
||||
&mut try(timer_parser),
|
||||
&mut try(open_parser),
|
||||
&mut try(close_parser),
|
||||
&mut try(explain_query_parser),
|
||||
|
|
|
@ -17,6 +17,10 @@ use linefeed::{
|
|||
Signal,
|
||||
};
|
||||
|
||||
use termion::{
|
||||
color,
|
||||
};
|
||||
|
||||
use self::InputResult::*;
|
||||
|
||||
use command_parser::{
|
||||
|
@ -95,7 +99,11 @@ impl InputReader {
|
|||
/// In this case, the input received so far is buffered internally.
|
||||
pub fn read_input(&mut self) -> Result<InputResult, cli::Error> {
|
||||
let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT };
|
||||
let line = match self.read_line(prompt) {
|
||||
let prompt = format!("{blue}{prompt}{reset}",
|
||||
blue = color::Fg(::BLUE),
|
||||
prompt = prompt,
|
||||
reset = color::Fg(color::Reset));
|
||||
let line = match self.read_line(prompt.as_str()) {
|
||||
UserAction::TextInput(s) => s,
|
||||
UserAction::Interrupt if self.in_process_cmd.is_some() => {
|
||||
self.in_process_cmd = None;
|
||||
|
|
|
@ -20,6 +20,8 @@ extern crate getopts;
|
|||
extern crate linefeed;
|
||||
extern crate rusqlite;
|
||||
extern crate tabwriter;
|
||||
extern crate termion;
|
||||
extern crate time;
|
||||
|
||||
extern crate mentat;
|
||||
extern crate edn;
|
||||
|
@ -29,6 +31,13 @@ extern crate mentat_db;
|
|||
|
||||
use getopts::Options;
|
||||
|
||||
use termion::{
|
||||
color,
|
||||
};
|
||||
|
||||
static BLUE: color::Rgb = color::Rgb(0x99, 0xaa, 0xFF);
|
||||
static GREEN: color::Rgb = color::Rgb(0x77, 0xFF, 0x99);
|
||||
|
||||
pub mod command_parser;
|
||||
pub mod input;
|
||||
pub mod repl;
|
||||
|
|
|
@ -14,6 +14,16 @@ use std::process;
|
|||
|
||||
use tabwriter::TabWriter;
|
||||
|
||||
use termion::{
|
||||
color,
|
||||
style,
|
||||
};
|
||||
|
||||
use time::{
|
||||
Duration,
|
||||
PreciseTime,
|
||||
};
|
||||
|
||||
use mentat::{
|
||||
Queryable,
|
||||
QueryExplanation,
|
||||
|
@ -66,10 +76,42 @@ lazy_static! {
|
|||
};
|
||||
}
|
||||
|
||||
fn eprint_out(s: &str) {
|
||||
eprint!("{green}{s}{reset}", green = color::Fg(::GREEN), s = s, reset = color::Fg(color::Reset));
|
||||
}
|
||||
|
||||
fn format_time(duration: Duration) {
|
||||
let m_micros = duration.num_microseconds();
|
||||
if let Some(micros) = m_micros {
|
||||
if micros < 10_000 {
|
||||
eprintln!("{bold}{micros}{reset}µs",
|
||||
bold = style::Bold,
|
||||
micros = micros,
|
||||
reset = style::Reset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let millis = duration.num_milliseconds();
|
||||
if millis < 5_000 {
|
||||
eprintln!("{bold}{millis}.{micros}{reset}ms",
|
||||
bold = style::Bold,
|
||||
millis = millis,
|
||||
micros = m_micros.unwrap_or(0) / 1000,
|
||||
reset = style::Reset);
|
||||
return;
|
||||
}
|
||||
eprintln!("{bold}{seconds}.{millis}{reset}s",
|
||||
bold = style::Bold,
|
||||
seconds = duration.num_seconds(),
|
||||
millis = millis,
|
||||
reset = style::Reset);
|
||||
}
|
||||
|
||||
/// Executes input and maintains state of persistent items.
|
||||
pub struct Repl {
|
||||
path: String,
|
||||
store: Store,
|
||||
timer_on: bool,
|
||||
}
|
||||
|
||||
impl Repl {
|
||||
|
@ -87,6 +129,7 @@ impl Repl {
|
|||
Ok(Repl{
|
||||
path: "".to_string(),
|
||||
store: store,
|
||||
timer_on: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -124,8 +167,13 @@ impl Repl {
|
|||
|
||||
/// Runs a single command input.
|
||||
fn handle_command(&mut self, cmd: Command) {
|
||||
let should_time = self.timer_on && cmd.is_timed();
|
||||
|
||||
let start = PreciseTime::now();
|
||||
|
||||
match cmd {
|
||||
Command::Help(args) => self.help_command(args),
|
||||
Command::Timer(on) => self.toggle_timer(on),
|
||||
Command::Open(db) => {
|
||||
match self.open(db) {
|
||||
Ok(_) => println!("Database {:?} opened", self.db_name()),
|
||||
|
@ -150,6 +198,13 @@ impl Repl {
|
|||
process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
let end = PreciseTime::now();
|
||||
if should_time {
|
||||
eprint_out("Run time");
|
||||
eprint!(": ");
|
||||
format_time(start.to(end));
|
||||
}
|
||||
}
|
||||
|
||||
fn open<T>(&mut self, path: T) -> ::mentat::errors::Result<()>
|
||||
|
@ -173,6 +228,10 @@ impl Repl {
|
|||
};
|
||||
}
|
||||
|
||||
fn toggle_timer(&mut self, on: bool) {
|
||||
self.timer_on = on;
|
||||
}
|
||||
|
||||
fn help_command(&self, args: Vec<String>) {
|
||||
if args.is_empty() {
|
||||
for (cmd, msg) in COMMAND_HELP.iter() {
|
||||
|
|
Loading…
Reference in a new issue