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:
Richard Newman 2018-02-15 07:36:18 -08:00 committed by GitHub
parent dec86bb2c5
commit 0dcb7df1c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 185 additions and 2 deletions

19
.vscode/launch.json vendored Normal file
View 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
View 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"
}
]
}

View file

@ -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]

View file

@ -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),

View file

@ -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;

View file

@ -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;

View file

@ -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() {