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 {
fn stdout(&mut self, out: &str);
fn stderr(&mut self, out: &str);
}
impl Host for Box<dyn Host> {
fn stdout(&mut self, out: &str) {
(**self).stdout(out)
}
fn stderr(&mut self, out: &str) {
(**self).stderr(out)
}
}
crate struct BasicHost;
@ -14,4 +19,8 @@ impl Host for BasicHost {
fn stdout(&mut self, out: &str) {
println!("{}", out)
}
fn stderr(&mut self, out: &str) {
eprintln!("{}", out)
}
}

View file

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

View file

@ -3,7 +3,6 @@
#[allow(unused)]
use crate::prelude::*;
use std::borrow::Cow::{self, Borrowed, Owned};
mod commands;
mod context;
@ -13,6 +12,7 @@ mod format;
mod object;
mod parser;
mod prelude;
mod shell;
use crate::commands::command::ReturnValue;
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::format::{EntriesListView, GenericView};
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::{ColorMode, Config, Editor, Helper, self};
use rustyline::{self, ColorMode, Config, Editor};
use std::collections::VecDeque;
use std::error::Error;
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>> {
let config = Config::builder().color_mode(ColorMode::Forced).build();
let h = MyHelper(
FilenameCompleter::new(),
MatchingBracketHighlighter::new(),
HistoryHinter {},
);
let mut rl: Editor<MyHelper> = Editor::with_config(config);
let h = crate::shell::Helper::new();
let mut rl: Editor<crate::shell::Helper> = Editor::with_config(config);
rl.set_helper(Some(h));
if rl.load_history("history.txt").is_err() {
println!("No previous history.");
@ -125,48 +77,25 @@ fn main() -> Result<(), Box<Error>> {
context.lock().unwrap().env.cwd().display().to_string()
));
match readline {
Ok(ref line) if line.trim() == "exit" => {
break;
}
Ok(line) => {
let result = crate::parser::shell_parser(&line)
.map_err(|e| ShellError::string(format!("{:?}", e)))?;
let parsed = result.1;
match process_line(readline, context.clone()) {
LineResult::Success(line) => {
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;
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
LineResult::FatalError(err) => {
context
.lock()
.unwrap()
.host
.stdout(&format!("A surprising fatal error occurred.\n{:?}", err));
}
}
}
@ -175,13 +104,81 @@ fn main() -> Result<(), Box<Error>> {
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(
line: String,
parsed: Vec<crate::parser::Item>,
input: VecDeque<Value>,
context: Arc<Mutex<Context>>,
) -> 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();
if command == &"format" {

View file

@ -120,6 +120,7 @@ impl Value {
}
}
#[allow(unused)]
crate fn as_bool(&self) -> Result<bool, ShellError> {
match self {
Value::Primitive(Primitive::Boolean(b)) => Ok(*b),
@ -143,6 +144,7 @@ impl Value {
Value::Primitive(Primitive::Int(s.into()))
}
#[allow(unused)]
crate fn bool(s: impl Into<bool>) -> Value {
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);
match v {
Value::Primitive(Primitive::Boolean(b)) => {
match (op, rhs) {
("-eq", Value::Primitive(Primitive::Boolean(b2))) => {
b == *b2
}
("-ne", Value::Primitive(Primitive::Boolean(b2))) => {
b != *b2
}
_ => false
}
}
Value::Primitive(Primitive::Bytes(i)) => {
match (op, rhs) {
("-lt", Value::Primitive(Primitive::Int(i2))) => {
i < (*i2 as u128)
}
("-gt", Value::Primitive(Primitive::Int(i2))) => {
i > (*i2 as u128)
}
("-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)
}
("-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
}
}
Value::Primitive(Primitive::Boolean(b)) => match (op, rhs) {
("-eq", Value::Primitive(Primitive::Boolean(b2))) => b == *b2,
("-ne", Value::Primitive(Primitive::Boolean(b2))) => b != *b2,
_ => false,
},
Value::Primitive(Primitive::Bytes(i)) => match (op, rhs) {
("-lt", Value::Primitive(Primitive::Int(i2))) => i < (*i2 as u128),
("-gt", Value::Primitive(Primitive::Int(i2))) => i > (*i2 as u128),
("-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),
("-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,
}
}

View file

@ -39,11 +39,12 @@ crate fn print_items(items: &[Item]) -> String {
}
impl Item {
crate fn name(&self) -> &str {
crate fn name(&self) -> Result<&str, ShellError> {
match self {
Item::Quoted(s) => s,
Item::Bare(s) => s,
Item::Boolean(_) | Item::Int(_) => unimplemented!(),
Item::Quoted(s) => Ok(s),
Item::Bare(s) => Ok(s),
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 {}