forked from extern/nushell
Refactor the repl loop
This commit is contained in:
parent
34a866df99
commit
98ab5e63fc
9
src/env/host.rs
vendored
9
src/env/host.rs
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ impl ShellError {
|
||||
error: self.error.copy(),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn description(&self) -> String {
|
||||
self.title.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ShellError {
|
||||
|
179
src/main.rs
179
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<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" {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
4
src/shell.rs
Normal file
@ -0,0 +1,4 @@
|
||||
crate mod completer;
|
||||
crate mod helper;
|
||||
|
||||
crate use helper::Helper;
|
41
src/shell/completer.rs
Normal file
41
src/shell/completer.rs
Normal 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
62
src/shell/helper.rs
Normal 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 {}
|
Loading…
Reference in New Issue
Block a user