Merge branch 'master' into touchups

This commit is contained in:
Jonathan Turner 2019-06-16 06:41:26 +12:00
commit 416d1c8cbb
19 changed files with 200 additions and 154 deletions

View File

@ -102,19 +102,16 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
cc.store(true, Ordering::SeqCst); cc.store(true, Ordering::SeqCst);
}) })
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
let mut ctrlcbreak = false;
loop { loop {
if ctrl_c.load(Ordering::SeqCst) { if ctrl_c.load(Ordering::SeqCst) {
ctrl_c.store(false, Ordering::SeqCst); ctrl_c.store(false, Ordering::SeqCst);
if let ShellError::String(s) = ShellError::string("CTRL-C") {
context.host.lock().unwrap().stdout(&format!("{:?}", s));
}
continue; continue;
} }
let (obj, cwd) = { let (obj, cwd) = {
let env = context.env.lock().unwrap(); let env = context.env.lock().unwrap();
let last = env.last().unwrap(); let last = env.back().unwrap();
(last.obj().clone(), last.path().display().to_string()) (last.obj().clone(), last.path().display().to_string())
}; };
let readline = match obj { let readline = match obj {
@ -134,6 +131,20 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
rl.add_history_entry(line.clone()); rl.add_history_entry(line.clone());
} }
LineResult::CtrlC => {
if ctrlcbreak {
std::process::exit(0);
} else {
context
.host
.lock()
.unwrap()
.stdout("CTRL-C pressed (again to quit)");
ctrlcbreak = true;
continue;
}
}
LineResult::Error(mut line, err) => match err { LineResult::Error(mut line, err) => match err {
ShellError::Diagnostic(diag) => { ShellError::Diagnostic(diag) => {
let host = context.host.lock().unwrap(); let host = context.host.lock().unwrap();
@ -177,6 +188,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
.stdout(&format!("A surprising fatal error occurred.\n{:?}", err)); .stdout(&format!("A surprising fatal error occurred.\n{:?}", err));
} }
} }
ctrlcbreak = false;
} }
rl.save_history("history.txt").unwrap(); rl.save_history("history.txt").unwrap();
@ -186,6 +198,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
enum LineResult { enum LineResult {
Success(String), Success(String),
Error(String, ShellError), Error(String, ShellError),
CtrlC,
Break, Break,
#[allow(unused)] #[allow(unused)]
@ -201,6 +214,7 @@ impl std::ops::Try for LineResult {
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::CtrlC => Ok(None),
LineResult::FatalError(err) => Err(err), LineResult::FatalError(err) => Err(err),
} }
} }
@ -259,27 +273,26 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
(None, _) => break, (None, _) => break,
(Some(ClassifiedCommand::Expr(_)), _) => { (Some(ClassifiedCommand::Expr(_)), _) => {
return LineResult::Error(line.clone(), ShellError::unimplemented( return LineResult::Error(
"Expression-only commands", line.clone(),
)) ShellError::unimplemented("Expression-only commands"),
)
} }
(_, Some(ClassifiedCommand::Expr(_))) => { (_, Some(ClassifiedCommand::Expr(_))) => {
return LineResult::Error(line.clone(), ShellError::unimplemented( return LineResult::Error(
"Expression-only commands", line.clone(),
)) ShellError::unimplemented("Expression-only commands"),
)
} }
(Some(ClassifiedCommand::Sink(_)), Some(_)) => { (Some(ClassifiedCommand::Sink(SinkCommand { name_span, .. })), Some(_)) => {
return LineResult::Error(line.clone(), ShellError::string("Commands like table, save, and autoview must come last in the pipeline")) return LineResult::Error(line.clone(), ShellError::maybe_labeled_error("Commands like table, save, and autoview must come last in the pipeline", "must come last", name_span));
} }
(Some(ClassifiedCommand::Sink(left)), None) => { (Some(ClassifiedCommand::Sink(left)), None) => {
let input_vec: Vec<Value> = input.objects.collect().await; let input_vec: Vec<Value> = input.objects.collect().await;
left.run( left.run(ctx, input_vec)?;
ctx,
input_vec,
)?;
break; break;
} }
@ -291,13 +304,12 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Err(err) => return LineResult::Error(line.clone(), err), Err(err) => return LineResult::Error(line.clone(), err),
}, },
( (Some(ClassifiedCommand::Internal(left)), Some(_)) => {
Some(ClassifiedCommand::Internal(left)), match left.run(ctx, input).await {
Some(_), Ok(val) => ClassifiedInputStream::from_input_stream(val),
) => match left.run(ctx, input).await { Err(err) => return LineResult::Error(line.clone(), err),
Ok(val) => ClassifiedInputStream::from_input_stream(val), }
Err(err) => return LineResult::Error(line.clone(), err), }
},
(Some(ClassifiedCommand::Internal(left)), None) => { (Some(ClassifiedCommand::Internal(left)), None) => {
match left.run(ctx, input).await { match left.run(ctx, input).await {
@ -314,13 +326,12 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
Err(err) => return LineResult::Error(line.clone(), err), Err(err) => return LineResult::Error(line.clone(), err),
}, },
( (Some(ClassifiedCommand::External(left)), Some(_)) => {
Some(ClassifiedCommand::External(left)), match left.run(ctx, input, StreamNext::Internal).await {
Some(_), Ok(val) => val,
) => match left.run(ctx, input, StreamNext::Internal).await { Err(err) => return LineResult::Error(line.clone(), err),
Ok(val) => val, }
Err(err) => return LineResult::Error(line.clone(), 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 {
@ -333,9 +344,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
LineResult::Success(line.clone()) LineResult::Success(line.clone())
} }
Err(ReadlineError::Interrupted) => { Err(ReadlineError::Interrupted) => LineResult::CtrlC,
LineResult::Error("".to_string(), ShellError::string("CTRL-C"))
}
Err(ReadlineError::Eof) => { Err(ReadlineError::Eof) => {
println!("CTRL-D"); println!("CTRL-D");
LineResult::Break LineResult::Break

View File

@ -5,7 +5,7 @@ use std::path::PathBuf;
pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
let env = args.env.lock().unwrap(); let env = args.env.lock().unwrap();
let latest = env.last().unwrap(); let latest = env.back().unwrap();
match latest.obj { match latest.obj {
Value::Filesystem => { Value::Filesystem => {
@ -13,17 +13,23 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
let path = match args.positional.first() { let path = match args.positional.first() {
None => match dirs::home_dir() { None => match dirs::home_dir() {
Some(o) => o, Some(o) => o,
_ => return Err(ShellError::string("Can not change to home directory")), _ => {
return Err(ShellError::maybe_labeled_error(
"Can not change to home directory",
"can not go to home",
args.name_span,
))
}
}, },
Some(v) => { Some(v) => {
let target = v.as_string()?.clone(); let target = v.as_string()?.clone();
match dunce::canonicalize(cwd.join(&target).as_path()) { match dunce::canonicalize(cwd.join(&target).as_path()) {
Ok(p) => p, Ok(p) => p,
Err(_) => { Err(_) => {
return Err(ShellError::labeled_error( return Err(ShellError::maybe_labeled_error(
"Can not change to directory", "Can not change to directory",
"directory not found", "directory not found",
args.positional[0].span.clone(), Some(args.positional[0].span.clone()),
)); ));
} }
} }
@ -35,10 +41,10 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(_) => {} Ok(_) => {}
Err(_) => { Err(_) => {
if args.positional.len() > 0 { if args.positional.len() > 0 {
return Err(ShellError::labeled_error( return Err(ShellError::maybe_labeled_error(
"Can not change to directory", "Can not change to directory",
"directory not found", "directory not found",
args.positional[0].span.clone(), Some(args.positional[0].span.clone()),
)); ));
} else { } else {
return Err(ShellError::string("Can not change to directory")); return Err(ShellError::string("Can not change to directory"));

View File

@ -4,6 +4,7 @@ use crate::parser::lexer::{Span, Spanned};
use crate::parser::registry::Args; use crate::parser::registry::Args;
use crate::prelude::*; use crate::prelude::*;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use futures::stream::StreamExt;
use futures_codec::{Decoder, Encoder, Framed}; use futures_codec::{Decoder, Encoder, Framed};
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::path::PathBuf; use std::path::PathBuf;
@ -109,44 +110,47 @@ impl InternalCommand {
context: &mut Context, context: &mut Context,
input: ClassifiedInputStream, input: ClassifiedInputStream,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let result = context.run_command( let mut result = context.run_command(
self.command, self.command,
self.name_span.clone(), self.name_span.clone(),
self.args, self.args,
input.objects, input.objects,
)?; )?;
let env = context.env.clone(); let mut stream = VecDeque::new();
while let Some(item) = result.next().await {
let stream = result.filter_map(move |v| match v { match item {
ReturnValue::Action(action) => match action { ReturnValue::Value(Value::Error(err)) => {
CommandAction::ChangePath(path) => { return Err(*err);
env.lock().unwrap().last_mut().map(|x| {
x.path = path;
x
});
futures::future::ready(None)
} }
CommandAction::Enter(obj) => { ReturnValue::Action(action) => match action {
let new_env = Environment { CommandAction::ChangePath(path) => {
obj: obj, context.env.lock().unwrap().back_mut().map(|x| {
path: PathBuf::from("/"), x.path = path;
}; x
env.lock().unwrap().push(new_env); });
futures::future::ready(None)
}
CommandAction::Exit => {
let mut v = env.lock().unwrap();
if v.len() == 1 {
std::process::exit(0);
} }
v.pop(); CommandAction::Enter(obj) => {
futures::future::ready(None) let new_env = Environment {
obj: obj,
path: PathBuf::from("/"),
};
context.env.lock().unwrap().push_back(new_env);
}
CommandAction::Exit => match context.env.lock().unwrap().pop_back() {
Some(Environment {
obj: Value::Filesystem,
..
}) => std::process::exit(0),
None => std::process::exit(-1),
_ => {}
},
},
ReturnValue::Value(v) => {
stream.push_back(v);
} }
}, }
}
ReturnValue::Value(v) => futures::future::ready(Some(v)),
});
Ok(stream.boxed() as InputStream) Ok(stream.boxed() as InputStream)
} }
} }
@ -265,7 +269,7 @@ impl ExternalCommand {
} }
process = Exec::shell(new_arg_string); process = Exec::shell(new_arg_string);
} }
process = process.cwd(context.env.lock().unwrap().first().unwrap().path()); process = process.cwd(context.env.lock().unwrap().front().unwrap().path());
let mut process = match stream_next { let mut process = match stream_next {
StreamNext::Last => process, StreamNext::Last => process,

View File

@ -8,7 +8,7 @@ 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<Vec<Environment>>>, pub env: Arc<Mutex<VecDeque<Environment>>>,
pub name_span: Option<Span>, pub name_span: Option<Span>,
pub positional: Vec<Spanned<Value>>, pub positional: Vec<Spanned<Value>>,
pub named: indexmap::IndexMap<String, Value>, pub named: indexmap::IndexMap<String, Value>,

View File

@ -7,14 +7,18 @@ use std::path::{Path, PathBuf};
pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
return Err(ShellError::string("open requires a filepath or url")); return Err(ShellError::maybe_labeled_error(
"open requires a path or url",
"missing path",
args.name_span,
));
} }
let cwd = args let cwd = args
.env .env
.lock() .lock()
.unwrap() .unwrap()
.first() .front()
.unwrap() .unwrap()
.path() .path()
.to_path_buf(); .to_path_buf();

View File

@ -5,15 +5,11 @@ use crate::prelude::*;
pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
if let Some(span) = args.name_span { return Err(ShellError::maybe_labeled_error(
return Err(ShellError::labeled_error( "First requires an amount",
"First requires an amount", "needs parameter",
"needs parameter", args.name_span,
span, ));
));
} else {
return Err(ShellError::string("first requires an amount."));
}
} }
let amount = args.positional[0].as_i64(); let amount = args.positional[0].as_i64();

View File

@ -15,7 +15,7 @@ fn from_node_to_value<'a, 'd>(n: &roxmltree::Node<'a, 'd>) -> Value {
.filter(|x| match x { .filter(|x| match x {
Value::Primitive(Primitive::String(f)) => { Value::Primitive(Primitive::String(f)) => {
if f.trim() == "" { if f.trim() == "" {
false false
} else { } else {
true true
} }
@ -57,11 +57,16 @@ pub fn from_xml_string_to_value(s: String) -> Value {
pub fn from_xml(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn from_xml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input; let out = args.input;
let span = args.name_span;
Ok(out Ok(out
.map(|a| match a { .map(move |a| match a {
Value::Primitive(Primitive::String(s)) => ReturnValue::Value(from_xml_string_to_value(s)), Value::Primitive(Primitive::String(s)) => {
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::string( ReturnValue::Value(from_xml_string_to_value(s))
}
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Trying to convert XML from non-string".to_string(), "Trying to convert XML from non-string".to_string(),
"given non-string",
span,
)))), )))),
}) })
.boxed()) .boxed())

View File

@ -1,17 +1,19 @@
use crate::errors::ShellError; use crate::errors::ShellError;
use crate::object::Value; use crate::object::Value;
use crate::parser::lexer::Span;
use crate::prelude::*; use crate::prelude::*;
fn get_member(path: &str, obj: &Value) -> Option<Value> { fn get_member(path: &str, span: Span, obj: &Value) -> Option<Value> {
let mut current = obj; let mut current = obj;
for p in path.split(".") { for p in path.split(".") {
match current.get_data_by_key(p) { match current.get_data_by_key(p) {
Some(v) => current = v, Some(v) => current = v,
None => { None => {
return Some(Value::Error(Box::new(ShellError::string(format!( return Some(Value::Error(Box::new(ShellError::labeled_error(
"Object field name not found: {}", "Unknown field",
p "object missing field",
))))) span,
))));
} }
} }
} }
@ -21,15 +23,11 @@ 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.len() == 0 { if args.positional.len() == 0 {
if let Some(span) = args.name_span { return Err(ShellError::maybe_labeled_error(
return Err(ShellError::labeled_error( "Get requires a field or field path",
"Get requires a field or field path", "needs parameter",
"needs parameter", args.name_span,
span, ));
));
} else {
return Err(ShellError::string("get requires fields."));
}
} }
let amount = args.positional[0].as_i64(); let amount = args.positional[0].as_i64();
@ -44,7 +42,11 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
.boxed()); .boxed());
} }
let fields: Result<Vec<String>, _> = args.positional.iter().map(|a| a.as_string()).collect(); let fields: Result<Vec<(String, Span)>, _> = args
.positional
.iter()
.map(|a| (a.as_string().map(|x| (x, a.span))))
.collect();
let fields = fields?; let fields = fields?;
let stream = args let stream = args
@ -52,7 +54,7 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
.map(move |item| { .map(move |item| {
let mut result = VecDeque::new(); let mut result = VecDeque::new();
for field in &fields { for field in &fields {
match get_member(field, &item) { match get_member(&field.0, field.1, &item) {
Some(Value::List(l)) => { Some(Value::List(l)) => {
for item in l { for item in l {
result.push_back(ReturnValue::Value(item.copy())); result.push_back(ReturnValue::Value(item.copy()));

View File

@ -7,8 +7,8 @@ use std::path::{Path, PathBuf};
pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
let env = args.env.lock().unwrap(); let env = args.env.lock().unwrap();
let path = env.last().unwrap().path.to_path_buf(); let path = env.back().unwrap().path.to_path_buf();
let obj = &env.last().unwrap().obj; let obj = &env.back().unwrap().obj;
let mut full_path = PathBuf::from(path); let mut full_path = PathBuf::from(path);
match &args.positional.get(0) { match &args.positional.get(0) {
Some(Spanned { Some(Spanned {
@ -31,7 +31,11 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
s.span, s.span,
)); ));
} else { } else {
return Err(ShellError::string(e.to_string())); return Err(ShellError::maybe_labeled_error(
e.to_string(),
e.to_string(),
args.name_span,
));
} }
} }
Ok(o) => o, Ok(o) => o,

View File

@ -6,14 +6,18 @@ use std::path::{Path, PathBuf};
pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
return Err(ShellError::string("open requires a filepath or url")); return Err(ShellError::maybe_labeled_error(
"Open requires a path or url",
"needs path or url",
args.name_span,
));
} }
let cwd = args let cwd = args
.env .env
.lock() .lock()
.unwrap() .unwrap()
.first() .front()
.unwrap() .unwrap()
.path() .path()
.to_path_buf(); .to_path_buf();

View File

@ -5,15 +5,11 @@ use crate::prelude::*;
pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
if let Some(span) = args.name_span { return Err(ShellError::maybe_labeled_error(
return Err(ShellError::labeled_error( "Pick requires fields",
"Pick requires fields", "needs parameter",
"needs parameter", args.name_span,
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

@ -5,15 +5,11 @@ use crate::prelude::*;
pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
if let Some(span) = args.name_span { return Err(ShellError::maybe_labeled_error(
return Err(ShellError::labeled_error( "Reject requires fields",
"Reject requires fields", "needs parameter",
"needs parameter", args.name_span,
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

@ -6,7 +6,11 @@ use std::path::{Path, PathBuf};
pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> { pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
return Err(ShellError::string("save requires a filepath")); return Err(ShellError::maybe_labeled_error(
"Save requires a filepath",
"needs path",
args.name_span,
));
} }
let cwd = args let cwd = args
@ -14,7 +18,7 @@ pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> {
.env .env
.lock() .lock()
.unwrap() .unwrap()
.first() .front()
.unwrap() .unwrap()
.path() .path()
.to_path_buf(); .to_path_buf();

View File

@ -6,14 +6,18 @@ use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
pub fn size(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn size(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.is_empty() { if args.positional.len() == 0 {
return Err(ShellError::string("size requires at least one file")); return Err(ShellError::maybe_labeled_error(
"Size requires a filepath",
"needs path",
args.name_span,
));
} }
let cwd = args let cwd = args
.env .env
.lock() .lock()
.unwrap() .unwrap()
.first() .front()
.unwrap() .unwrap()
.path() .path()
.to_path_buf(); .to_path_buf();

View File

@ -3,15 +3,11 @@ use crate::prelude::*;
pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn skip(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
if let Some(span) = args.name_span { return Err(ShellError::maybe_labeled_error(
return Err(ShellError::labeled_error( "Skip requires an amount",
"Skip requires an amount", "needs parameter",
"needs parameter", args.name_span,
span, ));
));
} else {
return Err(ShellError::string("skip requires an amount."));
}
} }
let amount = args.positional[0].as_i64(); let amount = args.positional[0].as_i64();

View File

@ -4,15 +4,11 @@ use prettyprint::PrettyPrinter;
pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.len() == 0 { if args.positional.len() == 0 {
if let Some(span) = args.name_span { return Err(ShellError::maybe_labeled_error(
return Err(ShellError::labeled_error( "View requires a filename",
"View requires a filename", "needs parameter",
"needs parameter", args.name_span,
span, ));
));
} else {
return Err(ShellError::string("view requires a filename."));
}
} }
let target = match args.positional[0].as_string() { let target = match args.positional[0].as_string() {
@ -34,7 +30,7 @@ pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
.env .env
.lock() .lock()
.unwrap() .unwrap()
.first() .front()
.unwrap() .unwrap()
.path() .path()
.to_path_buf(); .to_path_buf();

View File

@ -25,8 +25,12 @@ impl Command for Where {
} }
pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.positional.is_empty() { if args.positional.len() == 0 {
return Err(ShellError::string("select requires a field")); return Err(ShellError::maybe_labeled_error(
"Where requires a condition",
"needs condition",
args.name_span,
));
} }
let block = args.positional[0].as_block()?; let block = args.positional[0].as_block()?;

View File

@ -13,16 +13,18 @@ pub struct Context {
commands: IndexMap<String, Arc<dyn Command>>, commands: IndexMap<String, Arc<dyn Command>>,
sinks: IndexMap<String, Arc<dyn Sink>>, sinks: IndexMap<String, Arc<dyn Sink>>,
crate host: Arc<Mutex<dyn Host + Send>>, crate host: Arc<Mutex<dyn Host + Send>>,
crate env: Arc<Mutex<Vec<Environment>>>, crate env: Arc<Mutex<VecDeque<Environment>>>,
} }
impl Context { impl Context {
crate fn basic() -> Result<Context, Box<dyn Error>> { crate fn basic() -> Result<Context, Box<dyn Error>> {
let mut env = VecDeque::new();
env.push_back(Environment::basic()?);
Ok(Context { Ok(Context {
commands: indexmap::IndexMap::new(), commands: indexmap::IndexMap::new(),
sinks: indexmap::IndexMap::new(), sinks: indexmap::IndexMap::new(),
host: Arc::new(Mutex::new(crate::env::host::BasicHost)), host: Arc::new(Mutex::new(crate::env::host::BasicHost)),
env: Arc::new(Mutex::new(vec![Environment::basic()?])), env: Arc::new(Mutex::new(env)),
}) })
} }

View File

@ -53,6 +53,20 @@ impl ShellError {
) )
} }
crate fn maybe_labeled_error(
msg: impl Into<String>,
label: impl Into<String>,
span: Option<Span>,
) -> ShellError {
match span {
Some(span) => ShellError::diagnostic(
Diagnostic::new(Severity::Error, msg.into())
.with_label(Label::new_primary(span).with_message(label.into())),
),
None => ShellError::string(msg),
}
}
crate fn string(title: impl Into<String>) -> ShellError { crate fn string(title: impl Into<String>) -> ShellError {
ShellError::String(StringError::new(title.into(), Value::nothing())) ShellError::String(StringError::new(title.into(), Value::nothing()))
} }