Add pretty errors to commands

This commit is contained in:
Jonathan Turner 2019-06-08 10:35:07 +12:00
parent 2d13069ca3
commit e94d1d2758
26 changed files with 318 additions and 166 deletions

View file

@ -122,11 +122,12 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.add_history_entry(line.clone()); rl.add_history_entry(line.clone());
} }
LineResult::Error(err) => match err { LineResult::Error(mut line, err) => match err {
ShellError::Diagnostic(diag, source) => { ShellError::Diagnostic(diag) => {
let host = context.host.lock().unwrap(); let host = context.host.lock().unwrap();
let writer = host.err_termcolor(); let writer = host.err_termcolor();
let files = crate::parser::span::Files::new(source); line.push_str(" ");
let files = crate::parser::span::Files::new(line);
language_reporting::emit( language_reporting::emit(
&mut writer.lock(), &mut writer.lock(),
@ -149,7 +150,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
.unwrap() .unwrap()
.stdout(&format!("Missing property {}", subpath)), .stdout(&format!("Missing property {}", subpath)),
ShellError::String(s) => context.host.lock().unwrap().stdout(&format!("{:?}", s)), ShellError::String(_) => context.host.lock().unwrap().stdout(&format!("{}", err)),
}, },
LineResult::Break => { LineResult::Break => {
@ -172,7 +173,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
enum LineResult { enum LineResult {
Success(String), Success(String),
Error(ShellError), Error(String, ShellError),
Break, Break,
#[allow(unused)] #[allow(unused)]
@ -186,13 +187,13 @@ impl std::ops::Try for LineResult {
fn into_result(self) -> Result<Option<String>, ShellError> { fn into_result(self) -> Result<Option<String>, ShellError> {
match self { match self {
LineResult::Success(s) => Ok(Some(s)), LineResult::Success(s) => Ok(Some(s)),
LineResult::Error(s) => Err(s), LineResult::Error(_, s) => Err(s),
LineResult::Break => Ok(None), LineResult::Break => Ok(None),
LineResult::FatalError(err) => Err(err), LineResult::FatalError(err) => Err(err),
} }
} }
fn from_error(v: ShellError) -> Self { fn from_error(v: ShellError) -> Self {
LineResult::Error(v) LineResult::Error(String::new(), v)
} }
fn from_ok(v: Option<String>) -> Self { fn from_ok(v: Option<String>) -> Self {
@ -212,7 +213,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Ok(line) => { Ok(line) => {
let result = match crate::parser::parse(&line) { let result = match crate::parser::parse(&line) {
Err(err) => { Err(err) => {
return LineResult::Error(err); return LineResult::Error(line.to_string(), err);
} }
Ok(val) => val, Ok(val) => val,
@ -228,6 +229,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Some(ClassifiedCommand::External(_)) => {} Some(ClassifiedCommand::External(_)) => {}
_ => pipeline.commands.push(ClassifiedCommand::Sink(SinkCommand { _ => pipeline.commands.push(ClassifiedCommand::Sink(SinkCommand {
command: sink("autoview", autoview::autoview), command: sink("autoview", autoview::autoview),
name_span: None,
args: Args { args: Args {
positional: vec![], positional: vec![],
named: indexmap::IndexMap::new(), named: indexmap::IndexMap::new(),
@ -247,19 +249,19 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
(None, _) => break, (None, _) => break,
(Some(ClassifiedCommand::Expr(_)), _) => { (Some(ClassifiedCommand::Expr(_)), _) => {
return LineResult::Error(ShellError::unimplemented( return LineResult::Error(line.to_string(), ShellError::unimplemented(
"Expression-only commands", "Expression-only commands",
)) ))
} }
(_, Some(ClassifiedCommand::Expr(_))) => { (_, Some(ClassifiedCommand::Expr(_))) => {
return LineResult::Error(ShellError::unimplemented( return LineResult::Error(line.to_string(), ShellError::unimplemented(
"Expression-only commands", "Expression-only commands",
)) ))
} }
(Some(ClassifiedCommand::Sink(_)), Some(_)) => { (Some(ClassifiedCommand::Sink(_)), Some(_)) => {
return LineResult::Error(ShellError::string("Commands like table, save, and autoview must come last in the pipeline")) return LineResult::Error(line.to_string(), ShellError::string("Commands like table, save, and autoview must come last in the pipeline"))
} }
(Some(ClassifiedCommand::Sink(left)), None) => { (Some(ClassifiedCommand::Sink(left)), None) => {
@ -276,7 +278,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Some(ClassifiedCommand::External(_)), Some(ClassifiedCommand::External(_)),
) => match left.run(ctx, input).await { ) => match left.run(ctx, input).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val), Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(err), Err(err) => return LineResult::Error(line.to_string(), err),
}, },
( (
@ -284,13 +286,13 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Some(_), Some(_),
) => match left.run(ctx, input).await { ) => match left.run(ctx, input).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val), Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(err), Err(err) => return LineResult::Error(line.to_string(), err),
}, },
(Some(ClassifiedCommand::Internal(left)), None) => { (Some(ClassifiedCommand::Internal(left)), None) => {
match left.run(ctx, input).await { match left.run(ctx, input).await {
Ok(val) => ClassifiedInputStream::from_input_stream(val), Ok(val) => ClassifiedInputStream::from_input_stream(val),
Err(err) => return LineResult::Error(err), Err(err) => return LineResult::Error(line.to_string(), err),
} }
} }
@ -299,7 +301,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Some(ClassifiedCommand::External(_)), Some(ClassifiedCommand::External(_)),
) => match left.run(ctx, input, StreamNext::External).await { ) => match left.run(ctx, input, StreamNext::External).await {
Ok(val) => val, Ok(val) => val,
Err(err) => return LineResult::Error(err), Err(err) => return LineResult::Error(line.to_string(), err),
}, },
( (
@ -307,13 +309,13 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Some(_), Some(_),
) => match left.run(ctx, input, StreamNext::Internal).await { ) => match left.run(ctx, input, StreamNext::Internal).await {
Ok(val) => val, Ok(val) => val,
Err(err) => return LineResult::Error(err), Err(err) => return LineResult::Error(line.to_string(), err),
}, },
(Some(ClassifiedCommand::External(left)), None) => { (Some(ClassifiedCommand::External(left)), None) => {
match left.run(ctx, input, StreamNext::Last).await { match left.run(ctx, input, StreamNext::Last).await {
Ok(val) => val, Ok(val) => val,
Err(err) => return LineResult::Error(err), Err(err) => return LineResult::Error(line.to_string(), err),
} }
} }
} }
@ -321,7 +323,9 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
LineResult::Success(line.to_string()) LineResult::Success(line.to_string())
} }
Err(ReadlineError::Interrupted) => LineResult::Error(ShellError::string("CTRL-C")), Err(ReadlineError::Interrupted) => {
LineResult::Error("".to_string(), ShellError::string("CTRL-C"))
}
Err(ReadlineError::Eof) => { Err(ReadlineError::Eof) => {
println!("CTRL-D"); println!("CTRL-D");
LineResult::Break LineResult::Break
@ -365,7 +369,7 @@ fn classify_command(
( (
Expression { Expression {
expr: RawExpression::Leaf(Leaf::Bare(name)), expr: RawExpression::Leaf(Leaf::Bare(name)),
.. span,
}, },
args, args,
) => match context.has_command(&name.to_string()) { ) => match context.has_command(&name.to_string()) {
@ -381,6 +385,7 @@ fn classify_command(
Ok(ClassifiedCommand::Internal(InternalCommand { Ok(ClassifiedCommand::Internal(InternalCommand {
command, command,
name_span: Some(span.clone()),
args, args,
})) }))
} }
@ -395,7 +400,11 @@ fn classify_command(
None => Args::default(), None => Args::default(),
}; };
Ok(ClassifiedCommand::Sink(SinkCommand { command, args })) Ok(ClassifiedCommand::Sink(SinkCommand {
command,
name_span: Some(span.clone()),
args,
}))
} }
false => { false => {
let arg_list_strings: Vec<String> = match args { let arg_list_strings: Vec<String> = match args {

View file

@ -11,12 +11,34 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
}, },
Some(v) => { Some(v) => {
let target = v.as_string()?.clone(); let target = v.as_string()?.clone();
dunce::canonicalize(cwd.join(&target).as_path())? match dunce::canonicalize(cwd.join(&target).as_path()) {
Ok(p) => p,
Err(_) => {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
args.positional[0].span.clone(),
));
}
}
} }
}; };
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
let _ = env::set_current_dir(&path); match env::set_current_dir(&path) {
Ok(_) => {}
Err(_) => {
if args.positional.len() > 0 {
return Err(ShellError::labeled_error(
"Can not change to directory",
"directory not found",
args.positional[0].span.clone(),
));
} else {
return Err(ShellError::string("Can not change to directory"));
}
}
}
stream.push_back(ReturnValue::change_cwd(path)); stream.push_back(ReturnValue::change_cwd(path));
Ok(stream.boxed()) Ok(stream.boxed())
} }

View file

@ -1,5 +1,6 @@
use crate::commands::command::Sink; use crate::commands::command::Sink;
use crate::parser::ast::Expression; use crate::parser::ast::Expression;
use crate::parser::lexer::Span;
use crate::parser::registry::Args; use crate::parser::registry::Args;
use crate::prelude::*; use crate::prelude::*;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
@ -86,17 +87,19 @@ crate enum ClassifiedCommand {
crate struct SinkCommand { crate struct SinkCommand {
crate command: Arc<dyn Sink>, crate command: Arc<dyn Sink>,
crate name_span: Option<Span>,
crate args: Args, crate args: Args,
} }
impl SinkCommand { impl SinkCommand {
crate fn run(self, context: &mut Context, input: Vec<Value>) -> Result<(), ShellError> { crate fn run(self, context: &mut Context, input: Vec<Value>) -> Result<(), ShellError> {
context.run_sink(self.command, self.args, input) context.run_sink(self.command, self.name_span.clone(), self.args, input)
} }
} }
crate struct InternalCommand { crate struct InternalCommand {
crate command: Arc<dyn Command>, crate command: Arc<dyn Command>,
crate name_span: Option<Span>,
crate args: Args, crate args: Args,
} }
@ -106,7 +109,12 @@ impl InternalCommand {
context: &mut Context, context: &mut Context,
input: ClassifiedInputStream, input: ClassifiedInputStream,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let result = context.run_command(self.command, self.args, input.objects)?; let result = context.run_command(
self.command,
self.name_span.clone(),
self.args,
input.objects,
)?;
let env = context.env.clone(); let env = context.env.clone();
let stream = result.filter_map(move |v| match v { let stream = result.filter_map(move |v| match v {

View file

@ -1,56 +1,28 @@
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::object::Value; use crate::object::Value;
use crate::parser::lexer::Span;
use crate::parser::lexer::Spanned;
use crate::parser::CommandConfig; use crate::parser::CommandConfig;
use crate::prelude::*; use crate::prelude::*;
use core::future::Future;
use std::path::PathBuf; use std::path::PathBuf;
pub struct CommandArgs { pub struct CommandArgs {
pub host: Arc<Mutex<dyn Host + Send>>, pub host: Arc<Mutex<dyn Host + Send>>,
pub env: Arc<Mutex<Environment>>, pub env: Arc<Mutex<Environment>>,
pub positional: Vec<Value>, pub name_span: Option<Span>,
pub positional: Vec<Spanned<Value>>,
pub named: indexmap::IndexMap<String, Value>, pub named: indexmap::IndexMap<String, Value>,
pub input: InputStream, pub input: InputStream,
} }
impl CommandArgs {
crate fn from_context(
ctx: &'caller mut Context,
positional: Vec<Value>,
input: InputStream,
) -> CommandArgs {
CommandArgs {
host: ctx.host.clone(),
env: ctx.env.clone(),
positional,
named: indexmap::IndexMap::default(),
input,
}
}
}
pub struct SinkCommandArgs { pub struct SinkCommandArgs {
pub ctx: Context, pub ctx: Context,
pub positional: Vec<Value>, pub name_span: Option<Span>,
pub positional: Vec<Spanned<Value>>,
pub named: indexmap::IndexMap<String, Value>, pub named: indexmap::IndexMap<String, Value>,
pub input: Vec<Value>, pub input: Vec<Value>,
} }
impl SinkCommandArgs {
crate fn from_context(
ctx: &'caller mut Context,
positional: Vec<Value>,
input: Vec<Value>,
) -> SinkCommandArgs {
SinkCommandArgs {
ctx: ctx.clone(),
positional,
named: indexmap::IndexMap::default(),
input,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum CommandAction { pub enum CommandAction {
ChangeCwd(PathBuf), ChangeCwd(PathBuf),

View file

@ -4,7 +4,30 @@ use crate::prelude::*;
// TODO: "Amount remaining" wrapper // TODO: "Amount remaining" wrapper
pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
let amount = args.positional[0].as_i64()?; if args.positional.len() == 0 {
if let Some(span) = args.name_span {
return Err(ShellError::labeled_error(
"First requires an amount",
"needs parameter",
span,
));
} else {
return Err(ShellError::string("first requires an amount."));
}
}
let amount = args.positional[0].as_i64();
let amount = match amount {
Ok(o) => o,
Err(_) => {
return Err(ShellError::labeled_error(
"Value is not a number",
"expected integer",
args.positional[0].span,
))
}
};
let input = args.input; let input = args.input;

View file

@ -20,8 +20,16 @@ fn get_member(path: &str, obj: &Value) -> Option<Value> {
} }
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.is_empty() { if args.positional.len() == 0 {
return Err(ShellError::string("select requires a field")); if let Some(span) = args.name_span {
return Err(ShellError::labeled_error(
"Get requires a field or field path",
"needs parameter",
span,
));
} else {
return Err(ShellError::string("get requires fields."));
}
} }
let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect();

View file

@ -1,5 +1,6 @@
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::object::{dir_entry_dict, Primitive, Value}; use crate::object::{dir_entry_dict, Primitive, Value};
use crate::parser::lexer::Spanned;
use crate::prelude::*; use crate::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -7,12 +8,29 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
let cwd = args.env.lock().unwrap().cwd().to_path_buf(); let cwd = args.env.lock().unwrap().cwd().to_path_buf();
let mut full_path = PathBuf::from(cwd); let mut full_path = PathBuf::from(cwd);
match &args.positional.get(0) { match &args.positional.get(0) {
Some(Value::Primitive(Primitive::String(s))) => full_path.push(Path::new(s)), Some(Spanned {
item: Value::Primitive(Primitive::String(s)),
..
}) => full_path.push(Path::new(s)),
_ => {} _ => {}
} }
let entries = let entries = std::fs::read_dir(&full_path);
std::fs::read_dir(&full_path).map_err(|e| ShellError::string(format!("{:?}", e)))?;
let entries = match entries {
Err(e) => {
if let Some(s) = args.positional.get(0) {
return Err(ShellError::labeled_error(
e.to_string(),
e.to_string(),
s.span,
));
} else {
return Err(ShellError::string(e.to_string()));
}
}
Ok(o) => o,
};
let mut shell_entries = VecDeque::new(); let mut shell_entries = VecDeque::new();

View file

@ -1,5 +1,6 @@
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::object::{Primitive, Value}; use crate::object::{Primitive, Value};
use crate::parser::lexer::Spanned;
use crate::prelude::*; use crate::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -10,17 +11,44 @@ pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
let cwd = args.env.lock().unwrap().cwd().to_path_buf(); let cwd = args.env.lock().unwrap().cwd().to_path_buf();
let mut full_path = PathBuf::from(cwd); let mut full_path = PathBuf::from(cwd);
match &args.positional[0] {
Value::Primitive(Primitive::String(s)) => full_path.push(Path::new(s)),
_ => {}
}
let contents = std::fs::read_to_string(&full_path).unwrap(); let contents = match &args.positional[0].item {
Value::Primitive(Primitive::String(s)) => {
full_path.push(Path::new(&s));
match std::fs::read_to_string(&full_path) {
Ok(s) => s,
Err(_) => {
return Err(ShellError::labeled_error(
"File cound not be opened",
"file not found",
args.positional[0].span,
));
}
}
}
_ => {
return Err(ShellError::labeled_error(
"Expected string value for filename",
"expected filename",
args.positional[0].span,
));
}
};
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
let open_raw = match args.positional.get(1) { let open_raw = match args.positional.get(1) {
Some(Value::Primitive(Primitive::String(s))) if s == "--raw" => true, Some(Spanned {
item: Value::Primitive(Primitive::String(s)),
..
}) if s == "--raw" => true,
Some(v) => {
return Err(ShellError::labeled_error(
"Unknown flag for open",
"unknown flag",
v.span,
))
}
_ => false, _ => false,
}; };

View file

@ -4,8 +4,16 @@ use crate::object::Value;
use crate::prelude::*; use crate::prelude::*;
pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.is_empty() { if args.positional.len() == 0 {
return Err(ShellError::string("select requires a field")); if let Some(span) = args.name_span {
return Err(ShellError::labeled_error(
"Pick requires fields",
"needs parameter",
span,
));
} else {
return Err(ShellError::string("pick requires fields."));
}
} }
let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect();

View file

@ -4,8 +4,16 @@ use crate::object::Value;
use crate::prelude::*; use crate::prelude::*;
pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.is_empty() { if args.positional.len() == 0 {
return Err(ShellError::string("select requires a field")); if let Some(span) = args.name_span {
return Err(ShellError::labeled_error(
"Reject requires fields",
"needs parameter",
span,
));
} else {
return Err(ShellError::string("reject requires fields."));
}
} }
let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect();

View file

@ -1,7 +1,7 @@
use crate::commands::command::SinkCommandArgs; use crate::commands::command::SinkCommandArgs;
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::object::{Primitive, Value}; use crate::object::{Primitive, Value};
use crate::prelude::*; use crate::parser::lexer::Spanned;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> { pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
@ -11,13 +11,16 @@ pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
let cwd = args.ctx.env.lock().unwrap().cwd().to_path_buf(); let cwd = args.ctx.env.lock().unwrap().cwd().to_path_buf();
let mut full_path = PathBuf::from(cwd); let mut full_path = PathBuf::from(cwd);
match &args.positional[0] { match &(args.positional[0].item) {
Value::Primitive(Primitive::String(s)) => full_path.push(Path::new(s)), Value::Primitive(Primitive::String(s)) => full_path.push(Path::new(s)),
_ => {} _ => {}
} }
let save_raw = match args.positional.get(1) { let save_raw = match args.positional.get(1) {
Some(Value::Primitive(Primitive::String(s))) if s == "--raw" => true, Some(Spanned {
item: Value::Primitive(Primitive::String(s)),
..
}) if s == "--raw" => true,
_ => false, _ => false,
}; };

View file

@ -2,7 +2,30 @@ use crate::errors::ShellError;
use crate::prelude::*; use crate::prelude::*;
pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> {
let amount = args.positional[0].as_i64()?; if args.positional.len() == 0 {
if let Some(span) = args.name_span {
return Err(ShellError::labeled_error(
"Skip requires an amount",
"needs parameter",
span,
));
} else {
return Err(ShellError::string("skip requires an amount."));
}
}
let amount = args.positional[0].as_i64();
let amount = match amount {
Ok(o) => o,
Err(_) => {
return Err(ShellError::labeled_error(
"Value is not a number",
"expected integer",
args.positional[0].span,
))
}
};
let input = args.input; let input = args.input;

View file

@ -3,10 +3,31 @@ use crate::prelude::*;
use prettyprint::PrettyPrinter; use prettyprint::PrettyPrinter;
pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
let target = match args.positional.first() { if args.positional.len() == 0 {
// TODO: This needs better infra if let Some(span) = args.name_span {
None => return Err(ShellError::string(format!("cat must take one arg"))), return Err(ShellError::labeled_error(
Some(v) => v.as_string()?.clone(), "View requires a filename",
"needs parameter",
span,
));
} else {
return Err(ShellError::string("view requires a filename."));
}
}
let target = match args.positional[0].as_string() {
Ok(s) => s.clone(),
Err(e) => {
if let Some(span) = args.name_span {
return Err(ShellError::labeled_error(
"Expected a string",
"not a filename",
span,
));
} else {
return Err(e);
}
}
}; };
let cwd = args.env.lock().unwrap().cwd().to_path_buf(); let cwd = args.env.lock().unwrap().cwd().to_path_buf();

View file

@ -1,5 +1,6 @@
use crate::commands::command::Sink; use crate::commands::command::Sink;
use crate::commands::command::SinkCommandArgs; use crate::commands::command::SinkCommandArgs;
use crate::parser::lexer::Span;
use crate::parser::Args; use crate::parser::Args;
use crate::prelude::*; use crate::prelude::*;
@ -37,10 +38,6 @@ impl Context {
} }
} }
pub fn clone_sinks(&self) -> indexmap::IndexMap<String, Arc<dyn Sink>> {
self.sinks.clone()
}
crate fn has_sink(&self, name: &str) -> bool { crate fn has_sink(&self, name: &str) -> bool {
self.sinks.contains_key(name) self.sinks.contains_key(name)
} }
@ -52,11 +49,13 @@ impl Context {
crate fn run_sink( crate fn run_sink(
&mut self, &mut self,
command: Arc<dyn Sink>, command: Arc<dyn Sink>,
name_span: Option<Span>,
args: Args, args: Args,
input: Vec<Value>, input: Vec<Value>,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let command_args = SinkCommandArgs { let command_args = SinkCommandArgs {
ctx: self.clone(), ctx: self.clone(),
name_span,
positional: args.positional, positional: args.positional,
named: args.named, named: args.named,
input, input,
@ -80,12 +79,14 @@ impl Context {
crate fn run_command( crate fn run_command(
&mut self, &mut self,
command: Arc<dyn Command>, command: Arc<dyn Command>,
name_span: Option<Span>,
args: Args, args: Args,
input: InputStream, input: InputStream,
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let command_args = CommandArgs { let command_args = CommandArgs {
host: self.host.clone(), host: self.host.clone(),
env: self.env.clone(), env: self.env.clone(),
name_span,
positional: args.positional, positional: args.positional,
named: args.named, named: args.named,
input, input,

View file

@ -2,7 +2,7 @@ use crate::parser::lexer::{Span, SpannedToken};
#[allow(unused)] #[allow(unused)]
use crate::prelude::*; use crate::prelude::*;
use derive_new::new; use derive_new::new;
use language_reporting::Diagnostic; use language_reporting::{Diagnostic, Label, Severity};
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -11,13 +11,12 @@ pub enum ShellError {
String(StringError), String(StringError),
TypeError(String), TypeError(String),
MissingProperty { subpath: String, expr: String }, MissingProperty { subpath: String, expr: String },
Diagnostic(ShellDiagnostic, String), Diagnostic(ShellDiagnostic),
} }
impl ShellError { impl ShellError {
crate fn parse_error( crate fn parse_error(
error: lalrpop_util::ParseError<usize, SpannedToken, ShellError>, error: lalrpop_util::ParseError<usize, SpannedToken, ShellError>,
source: String,
) -> ShellError { ) -> ShellError {
use lalrpop_util::ParseError; use lalrpop_util::ParseError;
use language_reporting::*; use language_reporting::*;
@ -33,15 +32,26 @@ impl ShellError {
) )
.with_label(Label::new_primary(Span::from((start, end)))); .with_label(Label::new_primary(Span::from((start, end))));
ShellError::diagnostic(diagnostic, source) ShellError::diagnostic(diagnostic)
} }
ParseError::User { error } => error,
other => ShellError::string(format!("{:?}", other)), other => ShellError::string(format!("{:?}", other)),
} }
} }
crate fn diagnostic(diagnostic: Diagnostic<Span>, source: String) -> ShellError { crate fn diagnostic(diagnostic: Diagnostic<Span>) -> ShellError {
ShellError::Diagnostic(ShellDiagnostic { diagnostic }, source) ShellError::Diagnostic(ShellDiagnostic { diagnostic })
}
crate fn labeled_error(
msg: impl Into<String>,
label: impl Into<String>,
span: Span,
) -> ShellError {
ShellError::diagnostic(
Diagnostic::new(Severity::Error, msg.into())
.with_label(Label::new_primary(span).with_message(label.into())),
)
} }
crate fn string(title: impl Into<String>) -> ShellError { crate fn string(title: impl Into<String>) -> ShellError {
@ -134,7 +144,7 @@ impl std::fmt::Display for ShellError {
ShellError::String(s) => write!(f, "{}", &s.title), ShellError::String(s) => write!(f, "{}", &s.title),
ShellError::TypeError { .. } => write!(f, "TypeError"), ShellError::TypeError { .. } => write!(f, "TypeError"),
ShellError::MissingProperty { .. } => write!(f, "MissingProperty"), ShellError::MissingProperty { .. } => write!(f, "MissingProperty"),
ShellError::Diagnostic(_, _) => write!(f, "<diagnostic>"), ShellError::Diagnostic(_) => write!(f, "<diagnostic>"),
} }
} }
} }

View file

@ -1,5 +1,6 @@
use crate::object::Primitive; use crate::object::Primitive;
use crate::parser::ast; use crate::parser::ast;
use crate::parser::lexer::Spanned;
use crate::prelude::*; use crate::prelude::*;
use derive_new::new; use derive_new::new;
use indexmap::IndexMap; use indexmap::IndexMap;
@ -20,17 +21,25 @@ impl Scope {
} }
} }
crate fn evaluate_expr(expr: &ast::Expression, scope: &Scope) -> Result<Value, ShellError> { crate fn evaluate_expr(
expr: &ast::Expression,
scope: &Scope,
) -> Result<Spanned<Value>, ShellError> {
use ast::*; use ast::*;
match &expr.expr { match &expr.expr {
RawExpression::Call(_) => Err(ShellError::unimplemented("Evaluating call expression")), RawExpression::Call(_) => Err(ShellError::unimplemented("Evaluating call expression")),
RawExpression::Leaf(l) => Ok(evaluate_leaf(l)), RawExpression::Leaf(l) => Ok(Spanned::from_item(evaluate_leaf(l), expr.span.clone())),
RawExpression::Parenthesized(p) => evaluate_expr(&p.expr, scope), RawExpression::Parenthesized(p) => evaluate_expr(&p.expr, scope),
RawExpression::Flag(f) => Ok(Value::Primitive(Primitive::String(f.print()))), RawExpression::Flag(f) => Ok(Spanned::from_item(
Value::Primitive(Primitive::String(f.print())),
expr.span.clone(),
)),
RawExpression::Block(b) => evaluate_block(&b, scope), RawExpression::Block(b) => evaluate_block(&b, scope),
RawExpression::Path(p) => evaluate_path(&p, scope), RawExpression::Path(p) => evaluate_path(&p, scope),
RawExpression::Binary(b) => evaluate_binary(b, scope), RawExpression::Binary(b) => evaluate_binary(b, scope),
RawExpression::VariableReference(r) => evaluate_reference(r, scope), RawExpression::VariableReference(r) => {
evaluate_reference(r, scope).map(|x| Spanned::from_item(x, expr.span.clone()))
}
} }
} }
@ -59,12 +68,15 @@ fn evaluate_reference(r: &ast::Variable, scope: &Scope) -> Result<Value, ShellEr
} }
} }
fn evaluate_binary(binary: &ast::Binary, scope: &Scope) -> Result<Value, ShellError> { fn evaluate_binary(binary: &ast::Binary, scope: &Scope) -> Result<Spanned<Value>, ShellError> {
let left = evaluate_expr(&binary.left, scope)?; let left = evaluate_expr(&binary.left, scope)?;
let right = evaluate_expr(&binary.right, scope)?; let right = evaluate_expr(&binary.right, scope)?;
match left.compare(&binary.operator, &right) { match left.compare(&binary.operator, &right) {
Some(v) => Ok(Value::boolean(v)), Some(v) => Ok(Spanned::from_item(
Value::boolean(v),
binary.operator.span.clone(),
)),
None => Err(ShellError::TypeError(format!( None => Err(ShellError::TypeError(format!(
"Can't compare {} and {}", "Can't compare {} and {}",
left.type_name(), left.type_name(),
@ -73,13 +85,16 @@ fn evaluate_binary(binary: &ast::Binary, scope: &Scope) -> Result<Value, ShellEr
} }
} }
fn evaluate_block(block: &ast::Block, _scope: &Scope) -> Result<Value, ShellError> { fn evaluate_block(block: &ast::Block, _scope: &Scope) -> Result<Spanned<Value>, ShellError> {
Ok(Value::block(block.expr.clone())) Ok(Spanned::from_item(
Value::block(block.expr.clone()),
block.expr.span.clone(),
))
} }
fn evaluate_path(path: &ast::Path, scope: &Scope) -> Result<Value, ShellError> { fn evaluate_path(path: &ast::Path, scope: &Scope) -> Result<Spanned<Value>, ShellError> {
let head = path.head(); let head = path.head();
let mut value = &evaluate_expr(head, scope)?; let mut value = evaluate_expr(head, scope)?;
let mut seen = vec![]; let mut seen = vec![];
for name in path.tail() { for name in path.tail() {
@ -93,9 +108,9 @@ fn evaluate_path(path: &ast::Path, scope: &Scope) -> Result<Value, ShellError> {
subpath: itertools::join(seen, "."), subpath: itertools::join(seen, "."),
}); });
} }
Some(v) => value = v, Some(v) => value = Spanned::from_item(v.copy(), name.span.clone()),
} }
} }
Ok(value.copy()) Ok(value)
} }

View file

@ -6,9 +6,8 @@ crate mod tree;
use crate::prelude::*; use crate::prelude::*;
crate use entries::{EntriesListView, EntriesView}; crate use entries::EntriesView;
crate use generic::GenericView; crate use generic::GenericView;
crate use list::ListView;
crate use table::TableView; crate use table::TableView;
crate use tree::TreeView; crate use tree::TreeView;

View file

@ -55,36 +55,3 @@ impl RenderView for EntriesView {
Ok(()) Ok(())
} }
} }
pub struct EntriesListView {
values: VecDeque<Value>,
}
impl EntriesListView {
crate async fn from_stream(values: InputStream) -> EntriesListView {
EntriesListView {
values: values.collect().await,
}
}
}
impl RenderView for EntriesListView {
fn render_view(&self, host: &mut dyn Host) -> Result<(), ShellError> {
if self.values.len() == 0 {
return Ok(());
}
let last = self.values.len() - 1;
for (i, item) in self.values.iter().enumerate() {
let view = EntriesView::from_value(item);
view.render_view(host)?;
if i != last {
host.stdout("\n");
}
}
Ok(())
}
}

View file

@ -1,4 +1,4 @@
use crate::format::{EntriesView, ListView, RenderView, TableView, TreeView}; use crate::format::{EntriesView, RenderView, TableView};
use crate::object::Value; use crate::object::Value;
use crate::prelude::*; use crate::prelude::*;
use derive_new::new; use derive_new::new;

View file

@ -21,7 +21,9 @@ pub struct TreeView {
impl TreeView { impl TreeView {
fn from_value_helper(value: &Value, mut builder: &mut TreeBuilder) { fn from_value_helper(value: &Value, mut builder: &mut TreeBuilder) {
match value { match value {
Value::Primitive(p) => builder = builder.add_empty_child(p.format(None)), Value::Primitive(p) => {
let _ = builder.add_empty_child(p.format(None));
}
Value::Object(o) => { Value::Object(o) => {
for (k, v) in o.entries.iter() { for (k, v) in o.entries.iter() {
builder = builder.begin_child(k.name.display().to_string()); builder = builder.begin_child(k.name.display().to_string());

View file

@ -2,6 +2,7 @@ use crate::errors::ShellError;
use crate::evaluate::{evaluate_expr, Scope}; use crate::evaluate::{evaluate_expr, Scope};
use crate::object::DataDescriptor; use crate::object::DataDescriptor;
use crate::parser::ast::{self, Operator}; use crate::parser::ast::{self, Operator};
use crate::parser::lexer::Spanned;
use crate::prelude::*; use crate::prelude::*;
use ansi_term::Color; use ansi_term::Color;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -142,7 +143,7 @@ impl Deserialize<'de> for Block {
} }
impl Block { impl Block {
pub fn invoke(&self, value: &Value) -> Result<Value, ShellError> { pub fn invoke(&self, value: &Value) -> Result<Spanned<Value>, ShellError> {
let scope = Scope::new(value.copy()); let scope = Scope::new(value.copy());
evaluate_expr(&self.expression, &scope) evaluate_expr(&self.expression, &scope)
} }

View file

@ -26,7 +26,7 @@ pub fn parse(input: &str) -> Result<Pipeline, ShellError> {
match parser.parse(tokens) { match parser.parse(tokens) {
Ok(val) => Ok(val), Ok(val) => Ok(val),
Err(err) => Err(ShellError::parse_error(err, input.to_string())), Err(err) => Err(ShellError::parse_error(err)),
} }
} }
@ -39,11 +39,11 @@ mod tests {
fn assert_parse(source: &str, expected: Pipeline) { fn assert_parse(source: &str, expected: Pipeline) {
let parsed = match parse(source) { let parsed = match parse(source) {
Ok(p) => p, Ok(p) => p,
Err(ShellError::Diagnostic(diag, source)) => { Err(ShellError::Diagnostic(diag)) => {
use language_reporting::termcolor; use language_reporting::termcolor;
let writer = termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto); let writer = termcolor::StandardStream::stdout(termcolor::ColorChoice::Auto);
let files = crate::parser::span::Files::new(source); let files = crate::parser::span::Files::new(source.to_string());
language_reporting::emit( language_reporting::emit(
&mut writer.lock(), &mut writer.lock(),

View file

@ -558,13 +558,12 @@ impl Iterator for Lexer<'source> {
} }
} }
fn lex_error(range: &Range<usize>, source: &str) -> ShellError { fn lex_error(range: &Range<usize>, _source: &str) -> ShellError {
use language_reporting::*; use language_reporting::*;
ShellError::diagnostic( ShellError::diagnostic(
Diagnostic::new(Severity::Error, "Lex error") Diagnostic::new(Severity::Error, "Lex error")
.with_label(Label::new_primary(Span::new(range))), .with_label(Label::new_primary(Span::new(range))),
source.to_string(),
) )
} }

View file

@ -1,4 +1,5 @@
use crate::evaluate::{evaluate_expr, Scope}; use crate::evaluate::{evaluate_expr, Scope};
use crate::parser::lexer::Spanned;
use crate::prelude::*; use crate::prelude::*;
use indexmap::IndexMap; use indexmap::IndexMap;
@ -37,14 +38,18 @@ impl PositionalType {
} }
} }
crate fn evaluate(&self, arg: ast::Expression, scope: &Scope) -> Result<Value, ShellError> { crate fn evaluate(
&self,
arg: ast::Expression,
scope: &Scope,
) -> Result<Spanned<Value>, ShellError> {
match self { match self {
PositionalType::Value(_) => evaluate_expr(&arg, scope), PositionalType::Value(_) => evaluate_expr(&arg, scope),
PositionalType::Block(_) => match arg { PositionalType::Block(_) => match arg {
ast::Expression { ast::Expression {
expr: ast::RawExpression::Block(b), expr: ast::RawExpression::Block(b),
.. ..
} => Ok(Value::block(b.expr)), } => Ok(Spanned::from_item(Value::block(b.expr), arg.span.clone())),
ast::Expression { ast::Expression {
expr: ast::RawExpression::Binary(binary), expr: ast::RawExpression::Binary(binary),
.. ..
@ -52,11 +57,14 @@ impl PositionalType {
// TODO: Use original spans // TODO: Use original spans
let mut b = ast::ExpressionBuilder::new(); let mut b = ast::ExpressionBuilder::new();
if let Some(s) = binary.left.as_string() { if let Some(s) = binary.left.as_string() {
Ok(Value::block(b.binary(( Ok(Spanned::from_item(
&|b| b.path((&|b| b.var("it"), vec![s.clone()])), Value::block(b.binary((
&|_| binary.operator.clone(), &|b| b.path((&|b| b.var("it"), vec![s.clone()])),
&|_| binary.right.clone(), &|_| binary.operator.clone(),
)))) &|_| binary.right.clone(),
))),
arg.span.clone(),
))
} else { } else {
let mut b = ast::ExpressionBuilder::new(); let mut b = ast::ExpressionBuilder::new();
let expr = b.binary(( let expr = b.binary((
@ -65,10 +73,13 @@ impl PositionalType {
&|_| binary.right.clone(), &|_| binary.right.clone(),
)); ));
Ok(Value::block(expr)) Ok(Spanned::from_item(Value::block(expr), arg.span.clone()))
} }
} }
other => Ok(Value::block(other)), // other => other => {
let span = other.span.clone();
Ok(Spanned::from_item(Value::block(other), span))
}
}, },
} }
} }
@ -85,7 +96,7 @@ pub struct CommandConfig {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Args { pub struct Args {
pub positional: Vec<Value>, pub positional: Vec<Spanned<Value>>,
pub named: IndexMap<String, Value>, pub named: IndexMap<String, Value>,
} }
@ -95,7 +106,7 @@ impl CommandConfig {
args: impl Iterator<Item = &'expr ast::Expression>, args: impl Iterator<Item = &'expr ast::Expression>,
scope: &Scope, scope: &Scope,
) -> Result<Args, ShellError> { ) -> Result<Args, ShellError> {
let mut positional: Vec<Value> = vec![]; let mut positional: Vec<Spanned<Value>> = vec![];
let mut named: IndexMap<String, Value> = IndexMap::default(); let mut named: IndexMap<String, Value> = IndexMap::default();
let mut args: Vec<ast::Expression> = args.cloned().collect(); let mut args: Vec<ast::Expression> = args.cloned().collect();
@ -152,7 +163,7 @@ impl CommandConfig {
} }
if self.rest_positional { if self.rest_positional {
let rest: Result<Vec<Value>, _> = let rest: Result<Vec<Spanned<Value>>, _> =
args.map(|i| evaluate_expr(&i, &Scope::empty())).collect(); args.map(|i| evaluate_expr(&i, &Scope::empty())).collect();
positional.extend(rest?); positional.extend(rest?);
} else { } else {

View file

@ -7,7 +7,7 @@ crate use crate::errors::ShellError;
crate use crate::object::Value; crate use crate::object::Value;
crate use crate::parser::ast; crate use crate::parser::ast;
crate use crate::stream::{single_output, InputStream, OutputStream}; crate use crate::stream::{single_output, InputStream, OutputStream};
crate use futures::{FutureExt, SinkExt, StreamExt}; crate use futures::{FutureExt, StreamExt};
crate use std::collections::VecDeque; crate use std::collections::VecDeque;
crate use std::pin::Pin; crate use std::pin::Pin;
crate use std::sync::{Arc, Mutex}; crate use std::sync::{Arc, Mutex};

View file

@ -4,10 +4,6 @@ use futures::stream::BoxStream;
pub type InputStream = BoxStream<'static, Value>; pub type InputStream = BoxStream<'static, Value>;
pub type OutputStream = BoxStream<'static, ReturnValue>; pub type OutputStream = BoxStream<'static, ReturnValue>;
crate fn empty_stream() -> OutputStream {
VecDeque::new().boxed()
}
crate fn single_output(item: Value) -> OutputStream { crate fn single_output(item: Value) -> OutputStream {
let value = ReturnValue::Value(item); let value = ReturnValue::Value(item);
let mut vec = VecDeque::new(); let mut vec = VecDeque::new();