Create mentat command line.
* Create tools directory containing new crate for mentat_cli. * Add simple cli with mentat prompt.
This commit is contained in:
parent
c95ec13ffe
commit
9b30a2c0a7
6 changed files with 308 additions and 0 deletions
|
@ -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
23
tools/cli/Cargo.toml
Normal 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"
|
15
tools/cli/src/bin/mentat_cli.rs
Normal file
15
tools/cli/src/bin/mentat_cli.rs
Normal 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);
|
||||
}
|
117
tools/cli/src/mentat_cli/input.rs
Normal file
117
tools/cli/src/mentat_cli/input.rs
Normal 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() {
|
||||
}
|
||||
}
|
75
tools/cli/src/mentat_cli/lib.rs
Normal file
75
tools/cli/src/mentat_cli/lib.rs
Normal 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() {
|
||||
}
|
||||
}
|
75
tools/cli/src/mentat_cli/repl.rs
Normal file
75
tools/cli/src/mentat_cli/repl.rs
Normal 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() {
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue