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
1dc8a3eaa0
commit
b169e59825
6 changed files with 308 additions and 0 deletions
|
@ -63,3 +63,6 @@ path = "query-translator"
|
||||||
|
|
||||||
[dependencies.mentat_tx_parser]
|
[dependencies.mentat_tx_parser]
|
||||||
path = "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