diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..1b9e1e52 --- /dev/null +++ b/.vscode/launch.json @@ -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" + }, + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..6503d0ba --- /dev/null +++ b/.vscode/tasks.json @@ -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" + } + ] +} diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml index b5512d90..cfe76d31 100644 --- a/tools/cli/Cargo.toml +++ b/tools/cli/Cargo.toml @@ -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] diff --git a/tools/cli/src/mentat_cli/command_parser.rs b/tools/cli/src/mentat_cli/command_parser.rs index ebf11067..5cb64338 100644 --- a/tools/cli/src/mentat_cli/command_parser.rs +++ b/tools/cli/src/mentat_cli/command_parser.rs @@ -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 { 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 { }); spaces() .skip(token('.')) - .with(choice::<[&mut Parser>; 8], _> + .with(choice::<[&mut Parser>; 9], _> ([&mut try(help_parser), + &mut try(timer_parser), &mut try(open_parser), &mut try(close_parser), &mut try(explain_query_parser), diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs index 5cf07e49..dd08bdf8 100644 --- a/tools/cli/src/mentat_cli/input.rs +++ b/tools/cli/src/mentat_cli/input.rs @@ -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 { 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; diff --git a/tools/cli/src/mentat_cli/lib.rs b/tools/cli/src/mentat_cli/lib.rs index f15b273c..be08ce23 100644 --- a/tools/cli/src/mentat_cli/lib.rs +++ b/tools/cli/src/mentat_cli/lib.rs @@ -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; diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index 68bce64e..cfb77c6b 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -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(&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) { if args.is_empty() { for (cmd, msg) in COMMAND_HELP.iter() {