diff --git a/src/env/host.rs b/src/env/host.rs index acc474f41..736c6bb05 100644 --- a/src/env/host.rs +++ b/src/env/host.rs @@ -1,11 +1,16 @@ pub trait Host { fn stdout(&mut self, out: &str); + fn stderr(&mut self, out: &str); } impl Host for Box { 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) + } } diff --git a/src/errors.rs b/src/errors.rs index b4cc52023..e341d4f74 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -21,6 +21,10 @@ impl ShellError { error: self.error.copy(), } } + + crate fn description(&self) -> String { + self.title.clone() + } } impl std::fmt::Display for ShellError { diff --git a/src/main.rs b/src/main.rs index 085cc05f9..733851ebd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 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), ReadlineError> { - self.0.complete(line, pos, ctx) - } -} - -impl Hinter for MyHelper { - fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { - 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> { let config = Config::builder().color_mode(ColorMode::Forced).build(); - let h = MyHelper( - FilenameCompleter::new(), - MatchingBracketHighlighter::new(), - HistoryHinter {}, - ); - let mut rl: Editor = Editor::with_config(config); + let h = crate::shell::Helper::new(); + let mut rl: Editor = 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> { 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> { Ok(()) } +enum LineResult { + Success(String), + Error(String), + Break, + + #[allow(unused)] + FatalError(ShellError), +} + +fn process_line( + readline: Result, + context: Arc>, +) -> 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, input: VecDeque, context: Arc>, ) -> Result, 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" { diff --git a/src/object/base.rs b/src/object/base.rs index 3720407ca..63615814f 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -120,6 +120,7 @@ impl Value { } } + #[allow(unused)] crate fn as_bool(&self) -> Result { 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) -> 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, } } diff --git a/src/parser/parse.rs b/src/parser/parse.rs index 40e40198e..276c057a3 100644 --- a/src/parser/parse.rs +++ b/src/parser/parse.rs @@ -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))), } } } diff --git a/src/shell.rs b/src/shell.rs new file mode 100644 index 000000000..7a87fa0a9 --- /dev/null +++ b/src/shell.rs @@ -0,0 +1,4 @@ +crate mod completer; +crate mod helper; + +crate use helper::Helper; diff --git a/src/shell/completer.rs b/src/shell/completer.rs new file mode 100644 index 000000000..0f68b3354 --- /dev/null +++ b/src/shell/completer.rs @@ -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)> { + 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 + } +} diff --git a/src/shell/helper.rs b/src/shell/helper.rs new file mode 100644 index 000000000..aaa5bd9f3 --- /dev/null +++ b/src/shell/helper.rs @@ -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), ReadlineError> { + self.completer.complete(line, pos, ctx) + } +} + +impl Hinter for Helper { + fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { + 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 {}