Create mentat command line.

* Create tools directory containing new crate for mentat_cli.
* Add simple cli with mentat prompt.
This commit is contained in:
Emily Toop 2017-05-09 10:54:11 +01:00
parent c95ec13ffe
commit 9b30a2c0a7
6 changed files with 308 additions and 0 deletions

View file

@ -61,3 +61,6 @@ path = "query-translator"
[dependencies.mentat_tx_parser]
path = "tx-parser"
[dependencies.mentat_cli]
path = "tools/cli"

23
tools/cli/Cargo.toml Normal file
View file

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

View file

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

View file

@ -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<String>),
/// 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<Reader<DefaultTerminal>>,
}
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<String> {
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<String> {
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() {
}
}

View file

@ -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::<Vec<_>>();
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() {
}
}

View file

@ -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<String>) {
println!("{:?} {:?}", cmd, args);
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
}
}