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"
|
log = "0.3"
|
||||||
tabwriter = "1"
|
tabwriter = "1"
|
||||||
tempfile = "1.1"
|
tempfile = "1.1"
|
||||||
|
termion = "1"
|
||||||
|
time = "0.1"
|
||||||
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
|
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
|
||||||
|
|
||||||
[dependencies.rusqlite]
|
[dependencies.rusqlite]
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub static CLOSE_COMMAND: &'static str = &"close";
|
||||||
pub static LONG_QUERY_COMMAND: &'static str = &"query";
|
pub static LONG_QUERY_COMMAND: &'static str = &"query";
|
||||||
pub static SHORT_QUERY_COMMAND: &'static str = &"q";
|
pub static SHORT_QUERY_COMMAND: &'static str = &"q";
|
||||||
pub static SCHEMA_COMMAND: &'static str = &"schema";
|
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 LONG_TRANSACT_COMMAND: &'static str = &"transact";
|
||||||
pub static SHORT_TRANSACT_COMMAND: &'static str = &"t";
|
pub static SHORT_TRANSACT_COMMAND: &'static str = &"t";
|
||||||
pub static LONG_EXIT_COMMAND: &'static str = &"exit";
|
pub static LONG_EXIT_COMMAND: &'static str = &"exit";
|
||||||
|
@ -53,6 +54,7 @@ pub enum Command {
|
||||||
Open(String),
|
Open(String),
|
||||||
Query(String),
|
Query(String),
|
||||||
Schema,
|
Schema,
|
||||||
|
Timer(bool),
|
||||||
Transact(String),
|
Transact(String),
|
||||||
QueryExplain(String),
|
QueryExplain(String),
|
||||||
}
|
}
|
||||||
|
@ -69,6 +71,7 @@ impl Command {
|
||||||
&Command::QueryExplain(ref args) => {
|
&Command::QueryExplain(ref args) => {
|
||||||
edn::parse::value(&args).is_ok()
|
edn::parse::value(&args).is_ok()
|
||||||
},
|
},
|
||||||
|
&Command::Timer(_) |
|
||||||
&Command::Help(_) |
|
&Command::Help(_) |
|
||||||
&Command::Open(_) |
|
&Command::Open(_) |
|
||||||
&Command::Close |
|
&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 {
|
pub fn output(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
&Command::Query(ref args) => {
|
&Command::Query(ref args) => {
|
||||||
|
@ -85,6 +102,9 @@ impl Command {
|
||||||
&Command::Transact(ref args) => {
|
&Command::Transact(ref args) => {
|
||||||
format!(".{} {}", LONG_TRANSACT_COMMAND, args)
|
format!(".{} {}", LONG_TRANSACT_COMMAND, args)
|
||||||
},
|
},
|
||||||
|
&Command::Timer(on) => {
|
||||||
|
format!(".{} {}", LONG_TIMER_COMMAND, on)
|
||||||
|
},
|
||||||
&Command::Help(ref args) => {
|
&Command::Help(ref args) => {
|
||||||
format!(".{} {:?}", HELP_COMMAND, args)
|
format!(".{} {:?}", HELP_COMMAND, args)
|
||||||
},
|
},
|
||||||
|
@ -117,6 +137,13 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
|
||||||
Ok(Command::Help(args.clone()))
|
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)
|
let open_parser = string(OPEN_COMMAND)
|
||||||
.with(spaces())
|
.with(spaces())
|
||||||
.with(arguments())
|
.with(arguments())
|
||||||
|
@ -189,8 +216,9 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
|
||||||
});
|
});
|
||||||
spaces()
|
spaces()
|
||||||
.skip(token('.'))
|
.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(help_parser),
|
||||||
|
&mut try(timer_parser),
|
||||||
&mut try(open_parser),
|
&mut try(open_parser),
|
||||||
&mut try(close_parser),
|
&mut try(close_parser),
|
||||||
&mut try(explain_query_parser),
|
&mut try(explain_query_parser),
|
||||||
|
|
|
@ -17,6 +17,10 @@ use linefeed::{
|
||||||
Signal,
|
Signal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use termion::{
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
|
||||||
use self::InputResult::*;
|
use self::InputResult::*;
|
||||||
|
|
||||||
use command_parser::{
|
use command_parser::{
|
||||||
|
@ -95,7 +99,11 @@ impl InputReader {
|
||||||
/// In this case, the input received so far is buffered internally.
|
/// In this case, the input received so far is buffered internally.
|
||||||
pub fn read_input(&mut self) -> Result<InputResult, cli::Error> {
|
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 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::TextInput(s) => s,
|
||||||
UserAction::Interrupt if self.in_process_cmd.is_some() => {
|
UserAction::Interrupt if self.in_process_cmd.is_some() => {
|
||||||
self.in_process_cmd = None;
|
self.in_process_cmd = None;
|
||||||
|
|
|
@ -20,6 +20,8 @@ extern crate getopts;
|
||||||
extern crate linefeed;
|
extern crate linefeed;
|
||||||
extern crate rusqlite;
|
extern crate rusqlite;
|
||||||
extern crate tabwriter;
|
extern crate tabwriter;
|
||||||
|
extern crate termion;
|
||||||
|
extern crate time;
|
||||||
|
|
||||||
extern crate mentat;
|
extern crate mentat;
|
||||||
extern crate edn;
|
extern crate edn;
|
||||||
|
@ -29,6 +31,13 @@ extern crate mentat_db;
|
||||||
|
|
||||||
use getopts::Options;
|
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 command_parser;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod repl;
|
pub mod repl;
|
||||||
|
|
|
@ -14,6 +14,16 @@ use std::process;
|
||||||
|
|
||||||
use tabwriter::TabWriter;
|
use tabwriter::TabWriter;
|
||||||
|
|
||||||
|
use termion::{
|
||||||
|
color,
|
||||||
|
style,
|
||||||
|
};
|
||||||
|
|
||||||
|
use time::{
|
||||||
|
Duration,
|
||||||
|
PreciseTime,
|
||||||
|
};
|
||||||
|
|
||||||
use mentat::{
|
use mentat::{
|
||||||
Queryable,
|
Queryable,
|
||||||
QueryExplanation,
|
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.
|
/// Executes input and maintains state of persistent items.
|
||||||
pub struct Repl {
|
pub struct Repl {
|
||||||
path: String,
|
path: String,
|
||||||
store: Store,
|
store: Store,
|
||||||
|
timer_on: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repl {
|
impl Repl {
|
||||||
|
@ -87,6 +129,7 @@ impl Repl {
|
||||||
Ok(Repl{
|
Ok(Repl{
|
||||||
path: "".to_string(),
|
path: "".to_string(),
|
||||||
store: store,
|
store: store,
|
||||||
|
timer_on: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +167,13 @@ 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 start = PreciseTime::now();
|
||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
Command::Help(args) => self.help_command(args),
|
Command::Help(args) => self.help_command(args),
|
||||||
|
Command::Timer(on) => self.toggle_timer(on),
|
||||||
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()),
|
||||||
|
@ -150,6 +198,13 @@ impl Repl {
|
||||||
process::exit(0);
|
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<()>
|
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>) {
|
fn help_command(&self, args: Vec<String>) {
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
for (cmd, msg) in COMMAND_HELP.iter() {
|
for (cmd, msg) in COMMAND_HELP.iter() {
|
||||||
|
|
Loading…
Reference in a new issue