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"
tabwriter = "1"
tempfile = "1.1"
termion = "1"
time = "0.1"
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" }
[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 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),

View file

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

View file

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

View file

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