From 9b30a2c0a73cac2c25e184b5136b4f939c74d4da Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Tue, 9 May 2017 10:54:11 +0100 Subject: [PATCH] Create mentat command line. * Create tools directory containing new crate for mentat_cli. * Add simple cli with mentat prompt. --- Cargo.toml | 3 + tools/cli/Cargo.toml | 23 ++++++ tools/cli/src/bin/mentat_cli.rs | 15 ++++ tools/cli/src/mentat_cli/input.rs | 117 ++++++++++++++++++++++++++++++ tools/cli/src/mentat_cli/lib.rs | 75 +++++++++++++++++++ tools/cli/src/mentat_cli/repl.rs | 75 +++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 tools/cli/Cargo.toml create mode 100644 tools/cli/src/bin/mentat_cli.rs create mode 100644 tools/cli/src/mentat_cli/input.rs create mode 100644 tools/cli/src/mentat_cli/lib.rs create mode 100644 tools/cli/src/mentat_cli/repl.rs diff --git a/Cargo.toml b/Cargo.toml index 00c0ecf6..6cb47d11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,3 +61,6 @@ path = "query-translator" [dependencies.mentat_tx_parser] path = "tx-parser" + +[dependencies.mentat_cli] +path = "tools/cli" diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml new file mode 100644 index 00000000..0583e311 --- /dev/null +++ b/tools/cli/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mentat_cli" +version = "0.0.1" +workspace = "../.." + +[lib] +name = "mentat_cli" +path = "src/mentat_cli/lib.rs" + +[[bin]] +name = "mentat_cli" +doc = false +test = false + +[build-dependencies] +rustc-serialize = "0.3.24" + +[dependencies] +getopts = "0.2" +env_logger = "0.3" +linefeed = "0.1" +log = "0.3" +tempfile = "1.1" diff --git a/tools/cli/src/bin/mentat_cli.rs b/tools/cli/src/bin/mentat_cli.rs new file mode 100644 index 00000000..28e03579 --- /dev/null +++ b/tools/cli/src/bin/mentat_cli.rs @@ -0,0 +1,15 @@ +// Copyright 2017 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +extern crate mentat_cli; + +fn main() { + let status = mentat_cli::run(); + std::process::exit(status); +} diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs new file mode 100644 index 00000000..267b3cf4 --- /dev/null +++ b/tools/cli/src/mentat_cli/input.rs @@ -0,0 +1,117 @@ +// Copyright 2017 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +use std::io::{self, stdin, BufRead, BufReader}; + +use linefeed::Reader; +use linefeed::terminal::DefaultTerminal; + +use self::InputResult::*; + +/// Possible results from reading input from `InputReader` +#[derive(Clone, Debug)] +pub enum InputResult { + /// rusti command as input; (name, rest of line) + Command(String, Option), + /// An empty line + Empty, + /// Needs more input; i.e. there is an unclosed delimiter + More, + /// End of file reached + Eof, +} + +/// Reads input from `stdin` +pub struct InputReader { + buffer: String, + reader: Option>, +} + +impl InputReader { + /// Constructs a new `InputReader` reading from `stdin`. + pub fn new() -> InputReader { + let r = match Reader::new("mentat") { + Ok(mut r) => { + r.set_word_break_chars(" \t\n!\"#$%&'()*+,-./:;<=>?@[\\]^`"); + Some(r) + } + Err(_) => None + }; + + InputReader{ + buffer: String::new(), + reader: r, + } + } + + /// Returns whether the `InputReader` is reading from a TTY. + pub fn is_tty(&self) -> bool { + self.reader.is_some() + } + + /// Reads a single command, item, or statement from `stdin`. + /// Returns `More` if further input is required for a complete result. + /// In this case, the input received so far is buffered internally. + pub fn read_input(&mut self, prompt: &str) -> InputResult { + let line = match self.read_line(prompt) { + Some(s) => s, + None => return Eof, + }; + + self.buffer.push_str(&line); + + if self.buffer.is_empty() { + return Empty; + } + + self.add_history(&line); + + let res = More; + + match res { + More => (), + _ => self.buffer.clear(), + }; + + res + } + + fn read_line(&mut self, prompt: &str) -> Option { + match self.reader { + Some(ref mut r) => { + r.set_prompt(prompt); + r.read_line().ok().and_then(|line| line) + } + None => self.read_stdin() + } + } + + fn read_stdin(&self) -> Option { + let mut s = String::new(); + + match stdin().read_line(&mut s) { + Ok(0) | Err(_) => None, + Ok(_) => Some(s) + } + } + + fn add_history(&mut self, line: &str) { + if let Some(ref mut r) = self.reader { + r.add_history(line.to_owned()); + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} diff --git a/tools/cli/src/mentat_cli/lib.rs b/tools/cli/src/mentat_cli/lib.rs new file mode 100644 index 00000000..41d196fd --- /dev/null +++ b/tools/cli/src/mentat_cli/lib.rs @@ -0,0 +1,75 @@ +// Copyright 2017 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +#![crate_name = "mentat_cli"] + +#[macro_use] extern crate log; + +extern crate env_logger; +extern crate getopts; +extern crate linefeed; + +use getopts::Options; + +pub mod input; +pub mod repl; + +pub fn run() -> i32 { + env_logger::init().unwrap(); + + let args = std::env::args().collect::>(); + let mut opts = Options::new(); + + opts.optflag("h", "help", "Print this help message and exit"); + opts.optflag("v", "version", "Print version and exit"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(e) => { + println!("{}: {}", args[0], e); + return 1; + } + }; + + if matches.opt_present("version") { + print_version(); + return 0; + } + if matches.opt_present("help") { + print_usage(&args[0], &opts); + return 0; + } + + let mut repl = repl::Repl::new(); + repl.run(); + + 0 +} + +/// Returns a version string. +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} + +fn print_usage(arg0: &str, opts: &Options) { + print!("{}", opts.usage(&format!( + "Usage: {} [OPTIONS] [FILE]", arg0))); +} + +fn print_version() { + println!("mentat {}", version()); +} + + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs new file mode 100644 index 00000000..d7b95f58 --- /dev/null +++ b/tools/cli/src/mentat_cli/repl.rs @@ -0,0 +1,75 @@ +// Copyright 2017 Mozilla +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +use input::{InputReader}; +use input::InputResult::{Command, Empty, More, Eof}; + +/// Starting prompt +const DEFAULT_PROMPT: &'static str = "mentat=> "; +/// Prompt when further input is being read +const MORE_PROMPT: &'static str = "mentat.> "; +/// Prompt when a `.block` command is in effect +const BLOCK_PROMPT: &'static str = "mentat+> "; + +/// Executes input and maintains state of persistent items. +pub struct Repl { +} + +impl Repl { + /// Constructs a new `Repl`. + pub fn new() -> Repl { + Repl{} + } + + + /// Runs the REPL interactively. + pub fn run(&mut self) { + let mut more = false; + let mut input = InputReader::new(); + + loop { + let res = input.read_input(if more { MORE_PROMPT } else { DEFAULT_PROMPT }); + // let res = if self.read_block { + // self.read_block = false; + // input.read_block_input(BLOCK_PROMPT) + // } else { + // input.read_input(if more { MORE_PROMPT } else { DEFAULT_PROMPT }) + // }; + + match res { + Command(name, args) => { + debug!("read command: {} {:?}", name, args); + + more = false; + self.handle_command(name, args); + }, + Empty => (), + More => { more = true; }, + Eof => { + if input.is_tty() { + println!(""); + } + break; + } + }; + } + } + + /// Runs a single command input. + fn handle_command(&mut self, cmd: String, args: Option) { + println!("{:?} {:?}", cmd, args); + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +}