Refactor the repl loop

This commit is contained in:
Yehuda Katz 2019-05-16 14:43:36 -07:00
parent 34a866df99
commit 98ab5e63fc
8 changed files with 243 additions and 163 deletions

9
src/env/host.rs vendored
View File

@ -1,11 +1,16 @@
pub trait Host { pub trait Host {
fn stdout(&mut self, out: &str); fn stdout(&mut self, out: &str);
fn stderr(&mut self, out: &str);
} }
impl Host for Box<dyn Host> { impl Host for Box<dyn Host> {
fn stdout(&mut self, out: &str) { fn stdout(&mut self, out: &str) {
(**self).stdout(out) (**self).stdout(out)
} }
fn stderr(&mut self, out: &str) {
(**self).stderr(out)
}
} }
crate struct BasicHost; crate struct BasicHost;
@ -14,4 +19,8 @@ impl Host for BasicHost {
fn stdout(&mut self, out: &str) { fn stdout(&mut self, out: &str) {
println!("{}", out) println!("{}", out)
} }
fn stderr(&mut self, out: &str) {
eprintln!("{}", out)
}
} }

View File

@ -21,6 +21,10 @@ impl ShellError {
error: self.error.copy(), error: self.error.copy(),
} }
} }
crate fn description(&self) -> String {
self.title.clone()
}
} }
impl std::fmt::Display for ShellError { impl std::fmt::Display for ShellError {

View File

@ -3,7 +3,6 @@
#[allow(unused)] #[allow(unused)]
use crate::prelude::*; use crate::prelude::*;
use std::borrow::Cow::{self, Borrowed, Owned};
mod commands; mod commands;
mod context; mod context;
@ -13,6 +12,7 @@ mod format;
mod object; mod object;
mod parser; mod parser;
mod prelude; mod prelude;
mod shell;
use crate::commands::command::ReturnValue; use crate::commands::command::ReturnValue;
crate use crate::commands::command::{Command, CommandAction, CommandBlueprint}; crate use crate::commands::command::{Command, CommandAction, CommandBlueprint};
@ -21,13 +21,9 @@ crate use crate::env::{Environment, Host};
crate use crate::errors::ShellError; crate use crate::errors::ShellError;
crate use crate::format::{EntriesListView, GenericView}; crate use crate::format::{EntriesListView, GenericView};
use crate::object::Value; use crate::object::Value;
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::hint::{Hinter, HistoryHinter};
use ansi_term::Color;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::{ColorMode, Config, Editor, Helper, self}; use rustyline::{self, ColorMode, Config, Editor};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::error::Error; use std::error::Error;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -48,54 +44,10 @@ impl<T> MaybeOwned<'a, T> {
} }
} }
struct MyHelper(FilenameCompleter, MatchingBracketHighlighter, HistoryHinter);
impl Completer for MyHelper {
type Candidate = Pair;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Pair>), ReadlineError> {
self.0.complete(line, pos, ctx)
}
}
impl Hinter for MyHelper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.2.hint(line, pos, ctx)
}
}
impl Highlighter for MyHelper {
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
Owned("\x1b[32m".to_owned() + &prompt[0..prompt.len() - 2] + "\x1b[m> ")
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
}
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
self.1.highlight(line, pos)
}
fn highlight_char(&self, line: &str, pos: usize) -> bool {
self.1.highlight_char(line, pos)
}
}
impl Helper for MyHelper {}
fn main() -> Result<(), Box<Error>> { fn main() -> Result<(), Box<Error>> {
let config = Config::builder().color_mode(ColorMode::Forced).build(); let config = Config::builder().color_mode(ColorMode::Forced).build();
let h = MyHelper( let h = crate::shell::Helper::new();
FilenameCompleter::new(), let mut rl: Editor<crate::shell::Helper> = Editor::with_config(config);
MatchingBracketHighlighter::new(),
HistoryHinter {},
);
let mut rl: Editor<MyHelper> = Editor::with_config(config);
rl.set_helper(Some(h)); rl.set_helper(Some(h));
if rl.load_history("history.txt").is_err() { if rl.load_history("history.txt").is_err() {
println!("No previous history."); println!("No previous history.");
@ -125,48 +77,25 @@ fn main() -> Result<(), Box<Error>> {
context.lock().unwrap().env.cwd().display().to_string() context.lock().unwrap().env.cwd().display().to_string()
)); ));
match readline { match process_line(readline, context.clone()) {
Ok(ref line) if line.trim() == "exit" => { LineResult::Success(line) => {
break;
}
Ok(line) => {
let result = crate::parser::shell_parser(&line)
.map_err(|e| ShellError::string(format!("{:?}", e)))?;
let parsed = result.1;
rl.add_history_entry(line.as_ref()); rl.add_history_entry(line.as_ref());
let mut input = VecDeque::new();
for item in parsed {
input = process_command(
crate::parser::print_items(&item),
item.clone(),
input,
context.clone(),
)?;
}
if input.len() > 0 {
if equal_shapes(&input) {
format(crate::commands::to_array(input), context.clone());
} else {
format(input, context.clone());
}
}
} }
Err(ReadlineError::Interrupted) => {
println!("CTRL-C"); LineResult::Error(err) => {
context.lock().unwrap().host.stdout(&err);
}
LineResult::Break => {
break; break;
} }
Err(ReadlineError::Eof) => {
println!("CTRL-D"); LineResult::FatalError(err) => {
break; context
} .lock()
Err(err) => { .unwrap()
println!("Error: {:?}", err); .host
break; .stdout(&format!("A surprising fatal error occurred.\n{:?}", err));
} }
} }
} }
@ -175,13 +104,81 @@ fn main() -> Result<(), Box<Error>> {
Ok(()) Ok(())
} }
enum LineResult {
Success(String),
Error(String),
Break,
#[allow(unused)]
FatalError(ShellError),
}
fn process_line(
readline: Result<String, ReadlineError>,
context: Arc<Mutex<Context>>,
) -> LineResult {
match &readline {
Ok(line) if line.trim() == "exit" => LineResult::Break,
Ok(line) if line.trim() == "" => LineResult::Success(line.clone()),
Ok(line) => {
let result = match crate::parser::shell_parser(&line) {
Err(err) => {
return LineResult::Error(format!("{:?}", err));
}
Ok(val) => val,
};
let parsed = result.1;
let mut input = VecDeque::new();
for item in parsed {
input = match process_command(
crate::parser::print_items(&item),
item.clone(),
input,
context.clone(),
) {
Ok(val) => val,
Err(err) => return LineResult::Error(format!("{}", err.description())),
};
}
if input.len() > 0 {
if equal_shapes(&input) {
format(crate::commands::to_array(input), context.clone());
} else {
format(input, context.clone());
}
}
LineResult::Success(line.to_string())
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
LineResult::Break
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
LineResult::Break
}
Err(err) => {
println!("Error: {:?}", err);
LineResult::Break
}
}
}
fn process_command( fn process_command(
line: String, line: String,
parsed: Vec<crate::parser::Item>, parsed: Vec<crate::parser::Item>,
input: VecDeque<Value>, input: VecDeque<Value>,
context: Arc<Mutex<Context>>, context: Arc<Mutex<Context>>,
) -> Result<VecDeque<Value>, ShellError> { ) -> Result<VecDeque<Value>, ShellError> {
let command = &parsed[0].name(); let command = &parsed[0].name()?;
let arg_list = parsed[1..].iter().map(|i| i.as_value()).collect(); let arg_list = parsed[1..].iter().map(|i| i.as_value()).collect();
if command == &"format" { if command == &"format" {

View File

@ -120,6 +120,7 @@ impl Value {
} }
} }
#[allow(unused)]
crate fn as_bool(&self) -> Result<bool, ShellError> { crate fn as_bool(&self) -> Result<bool, ShellError> {
match self { match self {
Value::Primitive(Primitive::Boolean(b)) => Ok(*b), Value::Primitive(Primitive::Boolean(b)) => Ok(*b),
@ -143,6 +144,7 @@ impl Value {
Value::Primitive(Primitive::Int(s.into())) Value::Primitive(Primitive::Int(s.into()))
} }
#[allow(unused)]
crate fn bool(s: impl Into<bool>) -> Value { crate fn bool(s: impl Into<bool>) -> Value {
Value::Primitive(Primitive::Boolean(s.into())) Value::Primitive(Primitive::Boolean(s.into()))
} }
@ -213,74 +215,34 @@ crate fn find(obj: &Value, field: &str, op: &str, rhs: &Value) -> bool {
//println!("'{:?}' '{}' '{:?}'", v, op, rhs); //println!("'{:?}' '{}' '{:?}'", v, op, rhs);
match v { match v {
Value::Primitive(Primitive::Boolean(b)) => { Value::Primitive(Primitive::Boolean(b)) => match (op, rhs) {
match (op, rhs) { ("-eq", Value::Primitive(Primitive::Boolean(b2))) => b == *b2,
("-eq", Value::Primitive(Primitive::Boolean(b2))) => { ("-ne", Value::Primitive(Primitive::Boolean(b2))) => b != *b2,
b == *b2 _ => false,
} },
("-ne", Value::Primitive(Primitive::Boolean(b2))) => { Value::Primitive(Primitive::Bytes(i)) => match (op, rhs) {
b != *b2 ("-lt", Value::Primitive(Primitive::Int(i2))) => i < (*i2 as u128),
} ("-gt", Value::Primitive(Primitive::Int(i2))) => i > (*i2 as u128),
_ => false ("-le", Value::Primitive(Primitive::Int(i2))) => i <= (*i2 as u128),
} ("-ge", Value::Primitive(Primitive::Int(i2))) => i >= (*i2 as u128),
} ("-eq", Value::Primitive(Primitive::Int(i2))) => i == (*i2 as u128),
Value::Primitive(Primitive::Bytes(i)) => { ("-ne", Value::Primitive(Primitive::Int(i2))) => i != (*i2 as u128),
match (op, rhs) { _ => false,
("-lt", Value::Primitive(Primitive::Int(i2))) => { },
i < (*i2 as u128) Value::Primitive(Primitive::Int(i)) => match (op, rhs) {
} ("-lt", Value::Primitive(Primitive::Int(i2))) => i < *i2,
("-gt", Value::Primitive(Primitive::Int(i2))) => { ("-gt", Value::Primitive(Primitive::Int(i2))) => i > *i2,
i > (*i2 as u128) ("-le", Value::Primitive(Primitive::Int(i2))) => i <= *i2,
} ("-ge", Value::Primitive(Primitive::Int(i2))) => i >= *i2,
("-le", Value::Primitive(Primitive::Int(i2))) => { ("-eq", Value::Primitive(Primitive::Int(i2))) => i == *i2,
i <= (*i2 as u128) ("-ne", Value::Primitive(Primitive::Int(i2))) => i != *i2,
} _ => false,
("-ge", Value::Primitive(Primitive::Int(i2))) => { },
i >= (*i2 as u128) Value::Primitive(Primitive::String(s)) => match (op, rhs) {
} ("-eq", Value::Primitive(Primitive::String(s2))) => s == *s2,
("-eq", Value::Primitive(Primitive::Int(i2))) => { ("-ne", Value::Primitive(Primitive::String(s2))) => s != *s2,
i == (*i2 as u128) _ => false,
} },
("-ne", Value::Primitive(Primitive::Int(i2))) => {
i != (*i2 as u128)
}
_ => false
}
}
Value::Primitive(Primitive::Int(i)) => {
match (op, rhs) {
("-lt", Value::Primitive(Primitive::Int(i2))) => {
i < *i2
}
("-gt", Value::Primitive(Primitive::Int(i2))) => {
i > *i2
}
("-le", Value::Primitive(Primitive::Int(i2))) => {
i <= *i2
}
("-ge", Value::Primitive(Primitive::Int(i2))) => {
i >= *i2
}
("-eq", Value::Primitive(Primitive::Int(i2))) => {
i == *i2
}
("-ne", Value::Primitive(Primitive::Int(i2))) => {
i != *i2
}
_ => false
}
}
Value::Primitive(Primitive::String(s)) => {
match (op, rhs) {
("-eq", Value::Primitive(Primitive::String(s2))) => {
s == *s2
}
("-ne", Value::Primitive(Primitive::String(s2))) => {
s != *s2
}
_ => false
}
}
_ => false, _ => false,
} }
} }

View File

@ -39,11 +39,12 @@ crate fn print_items(items: &[Item]) -> String {
} }
impl Item { impl Item {
crate fn name(&self) -> &str { crate fn name(&self) -> Result<&str, ShellError> {
match self { match self {
Item::Quoted(s) => s, Item::Quoted(s) => Ok(s),
Item::Bare(s) => s, Item::Bare(s) => Ok(s),
Item::Boolean(_) | Item::Int(_) => unimplemented!(), Item::Boolean(i) => Err(ShellError::string(format!("{} is not a valid command", i))),
Item::Int(i) => Err(ShellError::string(format!("{} is not a valid command", i))),
} }
} }
} }

4
src/shell.rs Normal file
View File

@ -0,0 +1,4 @@
crate mod completer;
crate mod helper;
crate use helper::Helper;

41
src/shell/completer.rs Normal file
View File

@ -0,0 +1,41 @@
use rustyline::completion::{Candidate, Completer};
use rustyline::line_buffer::LineBuffer;
#[derive(Debug)]
crate struct NuCompleter;
impl Completer for NuCompleter {
type Candidate = NuPair;
fn complete(
&self,
_line: &str,
_pos: usize,
_context: &rustyline::Context,
) -> rustyline::Result<(usize, Vec<NuPair>)> {
Ok((
0,
vec![
NuPair("exit", "exit"),
NuPair("ls", "ls"),
NuPair("ps", "ps"),
],
))
}
fn update(&self, line: &mut LineBuffer, start: usize, elected: &str) {
let end = line.pos();
line.replace(start..end, elected)
}
}
crate struct NuPair(&'static str, &'static str);
impl Candidate for NuPair {
fn display(&self) -> &str {
self.0
}
fn replacement(&self) -> &str {
self.1
}
}

62
src/shell/helper.rs Normal file
View File

@ -0,0 +1,62 @@
use crate::shell::completer::{NuCompleter, NuPair};
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::hint::{Hinter, HistoryHinter};
use std::borrow::Cow::{self, Owned};
crate struct Helper {
completer: NuCompleter,
highlighter: MatchingBracketHighlighter,
hinter: HistoryHinter,
}
impl Helper {
crate fn new() -> Helper {
Helper {
completer: NuCompleter,
highlighter: MatchingBracketHighlighter::new(),
hinter: HistoryHinter {},
}
}
}
impl Completer for Helper {
type Candidate = NuPair;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<NuPair>), ReadlineError> {
self.completer.complete(line, pos, ctx)
}
}
impl Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx)
}
}
impl Highlighter for Helper {
fn highlight_prompt<'p>(&self, prompt: &'p str) -> Cow<'p, str> {
Owned("\x1b[32m".to_owned() + &prompt[0..prompt.len() - 2] + "\x1b[m> ")
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
}
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
self.highlighter.highlight(line, pos)
}
fn highlight_char(&self, line: &str, pos: usize) -> bool {
self.highlighter.highlight_char(line, pos)
}
}
impl rustyline::Helper for Helper {}