WIP improve error infrastructure

Also simplify commands and reduce papercuts
This commit is contained in:
Yehuda Katz 2019-07-03 13:31:15 -07:00
parent 5d73c73fe8
commit 34033afce4
46 changed files with 1087 additions and 520 deletions

View File

@ -159,6 +159,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
command("sysinfo", Box::new(sysinfo::sysinfo)),
command("cd", Box::new(cd::cd)),
command("view", Box::new(view::view)),
// command("skip", skip::Skip),
command("first", Box::new(first::first)),
command("size", Box::new(size::size)),
command("from-ini", Box::new(from_ini::from_ini)),
@ -167,7 +168,6 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
command("from-xml", Box::new(from_xml::from_xml)),
command("from-yaml", Box::new(from_yaml::from_yaml)),
command("get", Box::new(get::get)),
command("enter", Box::new(enter::enter)),
command("exit", Box::new(exit::exit)),
command("lines", Box::new(lines::lines)),
command("pick", Box::new(pick::pick)),
@ -180,11 +180,15 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
command("to-json", Box::new(to_json::to_json)),
command("to-toml", Box::new(to_toml::to_toml)),
command("sort-by", Box::new(sort_by::sort_by)),
command("sort-by", Box::new(sort_by::sort_by)),
command("inc", |x| plugin::plugin("inc".into(), x)),
command("sum", |x| plugin::plugin("sum".into(), x)),
Arc::new(Open),
Arc::new(Where),
Arc::new(Config),
Arc::new(SkipWhile),
command("sort-by", Box::new(sort_by::sort_by)),
Arc::new(Enter),
Arc::new(Skip),
]);
context.add_sinks(vec![
@ -392,7 +396,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
}
(Some(ClassifiedCommand::Sink(left)), None) => {
let input_vec: Vec<Value> = input.objects.collect().await;
let input_vec: Vec<Value> = input.objects.into_vec().await;
if let Err(err) = left.run(ctx, input_vec) {
return LineResult::Error(line.clone(), err);
}

View File

@ -1,3 +1,6 @@
#[macro_use]
crate mod macros;
crate mod args;
crate mod autoview;
crate mod cd;
@ -39,6 +42,8 @@ crate mod where_;
crate use command::command;
crate use config::Config;
crate use enter::Enter;
crate use open::Open;
crate use skip::Skip;
crate use skip_while::SkipWhile;
crate use where_::Where;

View File

@ -52,14 +52,14 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
}
}
}
stream.push_back(ReturnValue::change_cwd(path));
Ok(stream.boxed())
stream.push_back(ReturnSuccess::change_cwd(path));
Ok(stream.into())
}
_ => {
let mut stream = VecDeque::new();
match args.nth(0) {
None => {
stream.push_back(ReturnValue::change_cwd(PathBuf::from("/")));
stream.push_back(ReturnSuccess::change_cwd(PathBuf::from("/")));
}
Some(v) => {
let mut cwd = latest.path().to_path_buf();
@ -75,10 +75,10 @@ pub fn cd(args: CommandArgs) -> Result<OutputStream, ShellError> {
}
},
}
stream.push_back(ReturnValue::change_cwd(cwd));
stream.push_back(ReturnSuccess::change_cwd(cwd));
}
};
Ok(stream.boxed())
Ok(stream.into())
}
}
}

View File

@ -54,21 +54,39 @@ crate struct ClassifiedInputStream {
impl ClassifiedInputStream {
crate fn new() -> ClassifiedInputStream {
ClassifiedInputStream {
objects: VecDeque::new().boxed(),
objects: VecDeque::new().into(),
stdin: None,
}
}
crate fn from_input_stream(stream: InputStream) -> ClassifiedInputStream {
pub fn into_vec(self) -> impl std::future::Future<Output = Vec<Value>> {
self.objects.into_vec()
}
crate fn from_vec(stream: VecDeque<Value>) -> ClassifiedInputStream {
ClassifiedInputStream {
objects: stream,
objects: stream.into(),
stdin: None,
}
}
crate fn from_list(stream: Vec<Value>) -> ClassifiedInputStream {
ClassifiedInputStream {
objects: stream.into(),
stdin: None,
}
}
crate fn from_input_stream(stream: impl Into<InputStream>) -> ClassifiedInputStream {
ClassifiedInputStream {
objects: stream.into(),
stdin: None,
}
}
crate fn from_stdout(stdout: std::fs::File) -> ClassifiedInputStream {
ClassifiedInputStream {
objects: VecDeque::new().boxed(),
objects: VecDeque::new().into(),
stdin: Some(stdout),
}
}
@ -110,31 +128,23 @@ impl InternalCommand {
context: &mut Context,
input: ClassifiedInputStream,
) -> Result<InputStream, ShellError> {
let objects = if log_enabled!(log::Level::Trace) {
if log_enabled!(log::Level::Trace) {
trace!("->");
trace!("{}", self.command.name());
trace!("{:?}", self.args.debug());
let objects: Vec<_> = input.objects.collect().await;
trace!(
"input = {:#?}",
objects.iter().map(|o| o.debug()).collect::<Vec<_>>(),
);
VecDeque::from(objects).boxed()
} else {
input.objects
};
}
let mut result =
let objects: InputStream = trace_stream!("input" = input.objects);
let result =
context.run_command(self.command, self.name_span.clone(), self.args, objects)?;
let mut result = result.values;
let mut stream = VecDeque::new();
while let Some(item) = result.next().await {
match item {
ReturnValue::Value(Value::Error(err)) => {
return Err(*err);
}
ReturnValue::Action(action) => match action {
match item? {
ReturnSuccess::Action(action) => match action {
CommandAction::ChangePath(path) => {
context.env.lock().unwrap().back_mut().map(|x| {
x.path = path;
@ -158,12 +168,13 @@ impl InternalCommand {
},
},
ReturnValue::Value(v) => {
ReturnSuccess::Value(v) => {
stream.push_back(v);
}
}
}
Ok(stream.boxed() as InputStream)
Ok(stream.into())
}
}
@ -187,9 +198,11 @@ impl ExternalCommand {
input: ClassifiedInputStream,
stream_next: StreamNext,
) -> Result<ClassifiedInputStream, ShellError> {
let inputs: Vec<Value> = input.objects.collect().await;
let stdin = input.stdin;
let inputs: Vec<Value> = input.objects.into_vec().await;
trace!("{:?} -> {}", inputs, self.name);
trace!("-> {}", self.name);
trace!("inputs = {:?}", inputs);
let mut arg_string = format!("{}", self.name);
for arg in &self.args {
@ -298,7 +311,7 @@ impl ExternalCommand {
}
};
if let Some(stdin) = input.stdin {
if let Some(stdin) = stdin {
process = process.stdin(stdin);
}
@ -318,7 +331,9 @@ impl ExternalCommand {
let file = futures::io::AllowStdIo::new(stdout);
let stream = Framed::new(file, LinesCodec {});
let stream = stream.map(|line| Value::string(line.unwrap()));
Ok(ClassifiedInputStream::from_input_stream(stream.boxed()))
Ok(ClassifiedInputStream::from_input_stream(
stream.boxed() as BoxStream<'static, Value>
))
}
}
}

View File

@ -1,10 +1,11 @@
use crate::commands::command::SinkCommandArgs;
use crate::errors::ShellError;
use crate::errors::{labelled, ShellError};
use clipboard::{ClipboardContext, ClipboardProvider};
pub fn clip(args: SinkCommandArgs) -> Result<(), ShellError> {
let mut clip_context: ClipboardContext = ClipboardProvider::new().unwrap();
let mut new_copy_data = String::new();
if args.input.len() > 0 {
let mut first = true;
for i in args.input.iter() {
@ -13,18 +14,17 @@ pub fn clip(args: SinkCommandArgs) -> Result<(), ShellError> {
} else {
first = false;
}
match i.as_string() {
Ok(s) => new_copy_data.push_str(&s),
Err(_) => {
return Err(ShellError::maybe_labeled_error(
"Given non-string data",
"expected strings from pipeline",
args.name_span,
))
}
}
let string = i.as_string().map_err(labelled(
args.name_span,
"Given non-string data",
"expected strings from pipeline",
))?;
new_copy_data.push_str(&string);
}
}
clip_context.set_contents(new_copy_data).unwrap();
Ok(())

View File

@ -7,7 +7,7 @@ use crate::parser::{
use crate::prelude::*;
use getset::Getters;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{ops::Try, path::PathBuf};
#[derive(Getters)]
#[get = "crate"]
@ -60,14 +60,26 @@ pub enum CommandAction {
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ReturnValue {
pub enum ReturnSuccess {
Value(Value),
Action(CommandAction),
}
impl ReturnValue {
crate fn change_cwd(path: PathBuf) -> ReturnValue {
ReturnValue::Action(CommandAction::ChangePath(path))
pub type ReturnValue = Result<ReturnSuccess, ShellError>;
impl From<Value> for ReturnValue {
fn from(input: Value) -> ReturnValue {
Ok(ReturnSuccess::Value(input))
}
}
impl ReturnSuccess {
pub fn change_cwd(path: PathBuf) -> ReturnValue {
Ok(ReturnSuccess::Action(CommandAction::ChangePath(path)))
}
pub fn value(input: Value) -> ReturnValue {
Ok(ReturnSuccess::Value(input))
}
}
@ -78,8 +90,7 @@ pub trait Command {
fn config(&self) -> registry::CommandConfig {
registry::CommandConfig {
name: self.name().to_string(),
mandatory_positional: vec![],
optional_positional: vec![],
positional: vec![],
rest_positional: true,
named: indexmap::IndexMap::new(),
is_filter: true,
@ -97,8 +108,7 @@ pub trait Sink {
fn config(&self) -> registry::CommandConfig {
registry::CommandConfig {
name: self.name().to_string(),
mandatory_positional: vec![],
optional_positional: vec![],
positional: vec![],
rest_positional: true,
named: indexmap::IndexMap::new(),
is_filter: false,

View File

@ -1,10 +1,13 @@
#[macro_use]
use crate::prelude::*;
use crate::errors::ShellError;
use crate::object::config;
use crate::object::Value;
use crate::parser::registry::{CommandConfig, NamedType, NamedValue};
use crate::prelude::*;
use indexmap::IndexMap;
use log::trace;
use std::iter::FromIterator;
pub struct Config;
@ -29,8 +32,7 @@ impl Command for Config {
CommandConfig {
name: self.name().to_string(),
mandatory_positional: vec![],
optional_positional: vec![],
positional: vec![],
rest_positional: false,
named,
is_sink: true,
@ -54,8 +56,7 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
.ok_or_else(|| ShellError::string(&format!("Missing key {} in config", key)))?;
return Ok(
futures::stream::once(futures::future::ready(ReturnValue::Value(value.clone())))
.boxed(),
vec![value.clone()].into(), // futures::stream::once(futures::future::ready(ReturnSuccess::Value(value.clone()))).into(),
);
}
@ -65,12 +66,7 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
config::write_config(&result)?;
return Ok(
futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object(
result.into(),
))))
.boxed(),
);
return Ok(stream![Value::Object(result.into())].from_input_stream());
}
}
@ -79,12 +75,7 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
config::write_config(&result)?;
return Ok(
futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object(
result.into(),
))))
.boxed(),
);
return Ok(stream![Value::Object(result.into())].from_input_stream());
}
if let Some(v) = args.get("remove") {
@ -99,21 +90,12 @@ pub fn config(args: CommandArgs) -> Result<OutputStream, ShellError> {
)));
}
return Ok(
futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object(
result.into(),
))))
.boxed(),
);
let obj = VecDeque::from_iter(vec![Value::Object(result.into())]);
return Ok(obj.from_input_stream());
}
if args.len() == 0 {
return Ok(
futures::stream::once(futures::future::ready(ReturnValue::Value(Value::Object(
result.into(),
))))
.boxed(),
);
return Ok(vec![Value::Object(result.into())].into());
}
Err(ShellError::string(format!("Unimplemented")))

View File

@ -2,9 +2,31 @@ use crate::commands::command::CommandAction;
use crate::commands::open::{fetch, parse_as_value};
use crate::errors::ShellError;
use crate::object::{Primitive, Value};
use crate::parser::registry::{CommandConfig, PositionalType};
use crate::prelude::*;
use std::path::PathBuf;
pub struct Enter;
impl Command for Enter {
fn config(&self) -> CommandConfig {
CommandConfig {
name: self.name().to_string(),
positional: vec![PositionalType::mandatory("path", "Block")],
rest_positional: false,
named: indexmap::IndexMap::new(),
}
}
fn name(&self) -> &str {
"enter"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
enter(args)
}
}
pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.len() == 0 {
return Err(ShellError::maybe_labeled_error(
@ -67,16 +89,9 @@ pub fn enter(args: CommandArgs) -> Result<OutputStream, ShellError> {
}
};
match contents {
Value::Primitive(Primitive::String(x)) => {
stream.push_back(ReturnValue::Action(CommandAction::Enter(parse_as_value(
file_extension,
x,
span,
)?)));
}
x => stream.push_back(ReturnValue::Action(CommandAction::Enter(x))),
}
stream.push_back(Ok(ReturnSuccess::Action(CommandAction::Enter(
parse_as_value(file_extension, contents, span)?,
))));
Ok(stream.boxed())
Ok(stream.into())
}

View File

@ -3,7 +3,5 @@ use crate::errors::ShellError;
use crate::prelude::*;
pub fn exit(_args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new();
stream.push_back(ReturnValue::Action(CommandAction::Exit));
Ok(stream.boxed())
Ok(vec![Ok(ReturnSuccess::Action(CommandAction::Exit))].into())
}

View File

@ -27,8 +27,5 @@ pub fn first(args: CommandArgs) -> Result<OutputStream, ShellError> {
let input = args.input;
Ok(input
.take(amount as u64)
.map(|v| ReturnValue::Value(v))
.boxed())
Ok(OutputStream::from_input(input.values.take(amount as u64)))
}

View File

@ -30,22 +30,21 @@ pub fn from_ini(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input;
let span = args.name_span;
Ok(out
.values
.map(move |a| match a {
Value::Primitive(Primitive::String(s)) => match from_ini_string_to_value(s) {
Ok(x) => ReturnValue::Value(x),
Err(e) => {
ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Could not parse as INI",
format!("{:#?}", e),
span,
))))
}
Ok(x) => Ok(ReturnSuccess::Value(x)),
Err(e) => Err(ShellError::maybe_labeled_error(
"Could not parse as INI",
format!("{:#?}", e),
span,
)),
},
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -34,22 +34,21 @@ pub fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input;
let span = args.name_span;
Ok(out
.values
.map(move |a| match a {
Value::Primitive(Primitive::String(s)) => match from_json_string_to_value(s) {
Ok(x) => ReturnValue::Value(x),
Err(_) => {
ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Could not parse as JSON",
"piped data failed JSON parse",
span,
))))
}
Ok(x) => ReturnSuccess::value(x),
Err(_) => Err(ShellError::maybe_labeled_error(
"Could not parse as JSON",
"piped data failed JSON parse",
span,
)),
},
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -33,22 +33,21 @@ pub fn from_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input;
let span = args.name_span;
Ok(out
.values
.map(move |a| match a {
Value::Primitive(Primitive::String(s)) => match from_toml_string_to_value(s) {
Ok(x) => ReturnValue::Value(x),
Err(_) => {
ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Could not parse as TOML",
"piped data failed TOML parse",
span,
))))
}
Ok(x) => ReturnSuccess::value(x),
Err(_) => Err(ShellError::maybe_labeled_error(
"Could not parse as TOML",
"piped data failed TOML parse",
span,
)),
},
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -52,22 +52,21 @@ pub fn from_xml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input;
let span = args.name_span;
Ok(out
.values
.map(move |a| match a {
Value::Primitive(Primitive::String(s)) => match from_xml_string_to_value(s) {
Ok(x) => ReturnValue::Value(x),
Err(_) => {
ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Could not parse as XML",
"piped data failed XML parse",
span,
))))
}
Ok(x) => ReturnSuccess::value(x),
Err(_) => Err(ShellError::maybe_labeled_error(
"Could not parse as XML",
"piped data failed XML parse",
span,
)),
},
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -43,22 +43,21 @@ pub fn from_yaml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input;
let span = args.name_span;
Ok(out
.values
.map(move |a| match a {
Value::Primitive(Primitive::String(s)) => match from_yaml_string_to_value(s) {
Ok(x) => ReturnValue::Value(x),
Err(_) => {
ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Could not parse as YAML",
"piped data failed YAML parse",
span,
))))
}
Ok(x) => ReturnSuccess::value(x),
Err(_) => Err(ShellError::maybe_labeled_error(
"Could not parse as YAML",
"piped data failed YAML parse",
span,
)),
},
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -3,22 +3,22 @@ use crate::object::Value;
use crate::parser::Span;
use crate::prelude::*;
fn get_member(path: &str, span: Span, obj: &Value) -> Option<Value> {
fn get_member(path: &str, span: Span, obj: &Value) -> Result<Value, ShellError> {
let mut current = obj;
for p in path.split(".") {
match current.get_data_by_key(p) {
Some(v) => current = v,
None => {
return Some(Value::Error(Box::new(ShellError::labeled_error(
return Err(ShellError::labeled_error(
"Unknown field",
"object missing field",
span,
))));
));
}
}
}
Some(current.copy())
Ok(current.copy())
}
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
@ -36,10 +36,10 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
if let Ok(amount) = amount {
return Ok(args
.input
.values
.skip(amount as u64)
.take(1)
.map(|v| ReturnValue::Value(v))
.boxed());
.from_input_stream());
}
let fields: Result<Vec<(String, Span)>, _> = args
@ -51,17 +51,18 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
let stream = args
.input
.values
.map(move |item| {
let mut result = VecDeque::new();
for field in &fields {
match get_member(&field.0, field.1, &item) {
Some(Value::List(l)) => {
Ok(Value::List(l)) => {
for item in l {
result.push_back(ReturnValue::Value(item.copy()));
result.push_back(ReturnSuccess::value(item.copy()));
}
}
Some(x) => result.push_back(ReturnValue::Value(x.copy())),
None => {}
Ok(x) => result.push_back(ReturnSuccess::value(x.copy())),
Err(_) => {}
}
}
@ -69,5 +70,5 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
})
.flatten();
Ok(stream.boxed())
Ok(stream.to_output_stream())
}

View File

@ -10,6 +10,7 @@ pub fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
let span = args.name_span;
let stream = input
.values
.map(move |v| match v {
Value::Primitive(Primitive::String(s)) => {
let split_result: Vec<_> = s.lines().filter(|s| s.trim() != "").collect();
@ -18,7 +19,7 @@ pub fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut result = VecDeque::new();
for s in split_result {
result.push_back(ReturnValue::Value(Value::Primitive(Primitive::String(
result.push_back(ReturnSuccess::value(Value::Primitive(Primitive::String(
s.into(),
))));
}
@ -26,17 +27,15 @@ pub fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
}
_ => {
let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new(
ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
),
))));
result.push_back(Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)));
result
}
})
.flatten();
Ok(stream.boxed())
Ok(stream.to_output_stream())
}

View File

@ -45,9 +45,9 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
for entry in entries {
let value = Value::Object(dir_entry_dict(&entry?)?);
shell_entries.push_back(ReturnValue::Value(value))
shell_entries.push_back(ReturnSuccess::value(value))
}
Ok(shell_entries.boxed())
Ok(shell_entries.to_output_stream())
}
_ => {
let mut entries = VecDeque::new();
@ -97,7 +97,11 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
return Err(ShellError::maybe_labeled_error(
"Index not closed",
format!("path missing closing ']'"),
if args.len() > 0 { Some(args.nth(0).unwrap().span) } else { args.name_span },
if args.len() > 0 {
Some(args.nth(0).unwrap().span)
} else {
args.name_span
},
))
}
}
@ -123,14 +127,14 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
match viewed {
Value::List(l) => {
for item in l {
entries.push_back(ReturnValue::Value(item.copy()));
entries.push_back(ReturnSuccess::value(item.copy()));
}
}
x => {
entries.push_back(ReturnValue::Value(x.clone()));
entries.push_back(ReturnSuccess::value(x.clone()));
}
}
Ok(entries.boxed())
Ok(entries.to_output_stream())
}
}
}

326
src/commands/macros.rs Normal file
View File

@ -0,0 +1,326 @@
#[macro_export]
macro_rules! command {
(
Named { $export:tt $args:ident $body:block }
Positional { $($number:tt)* }
Rest {}
CommandConfig {
name: $config_name:tt,
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
optional_positional: vec![ $($optional_positional:tt)* ],
rest_positional: $rest_positional:tt,
named: {
$(
($named_param:tt : $named_type:tt)
)*
}
}
Function {
$( ( $param_name:tt : $param_type:tt ) )*
}
Extract {
$($extract:tt)*
}
) => {
#[allow(non_camel_case_types)]
pub struct $export;
impl Command for $export {
fn run(&self, $args: CommandArgs) -> Result<OutputStream, ShellError> {
fn command($args: CommandArgs, ( $($param_name),*, ): ( $($param_type),*, )) -> Result<OutputStream, ShellError> {
let output = $body;
Ok(output.boxed().to_output_stream())
}
let tuple = ( $($extract),*, );
command( $args, tuple )
}
fn name(&self) -> &str {
stringify!($config_name)
}
fn config(&self) -> $crate::parser::registry::CommandConfig {
$crate::parser::registry::CommandConfig {
name: self.name().to_string(),
positional: vec![$($mandatory_positional)*],
rest_positional: false,
named: {
use $crate::parser::registry::{NamedType, NamedValue};
#[allow(unused_mut)]
let mut named: indexmap::IndexMap<String, NamedType> = indexmap::IndexMap::new();
$(
named.insert(stringify!($named_param).to_string(), NamedType::$named_type);
)*
named
}
}
}
}
};
// switch
(
Named { $export:tt $args:ident $body:block }
Positional { $($positional_count:tt)* }
Rest { , -- $param_name:ident : Switch $($rest:tt)* }
CommandConfig {
name: $config_name:tt,
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
optional_positional: vec![ $($optional_positional:tt)* ],
rest_positional: $rest_positional:tt,
named: {
$($config_named:tt)*
}
}
Function {
$($function:tt)*
}
Extract {
$($extract:tt)*
}
) => {
command!(
Named { $export $args $body }
Positional { $($positional_count)* + 1 }
Rest { $($rest)* }
CommandConfig {
name: $config_name,
mandatory_positional: vec![ $($mandatory_positional)* ],
optional_positional: vec![ $($optional_positional)* ],
rest_positional: $rest_positional,
named: {
$($config_named)*
($param_name : Switch)
}
}
Function {
$($function)* ($param_name : Switch)
}
Extract {
($($extract)* {
use std::convert::TryInto;
$args.get(stringify!($param_name)).clone().try_into()?
})
}
);
};
// mandatory named arguments
(
Named { $export:tt $args:ident $body:block }
Positional { $($positional_count:tt)* }
Rest { , -- $param_name:ident : $param_kind:tt $($rest:tt)* }
CommandConfig {
name: $config_name:tt,
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
optional_positional: vec![ $($optional_positional:tt)* ],
rest_positional: $rest_positional:tt,
named: {
$($config_named:tt)*
}
}
Function {
$($function:tt)*
}
Extract {
$($extract:tt)*
}
) => {
command!(
Named { $export $args $body }
Positional { $($positional_count)* + 1 }
Rest { $($rest)* }
CommandConfig {
name: $config_name,
mandatory_positional: vec![ $($mandatory_positional)* ],
optional_positional: vec![ $($optional_positional)* ],
rest_positional: $rest_positional,
named: {
$($config_named)*
($param_name : Mandatory(NamedValue::Single))
}
}
Function {
$($function)* ($param_name : $param_kind)
}
Extract {
($($extract)* {
use std::convert::TryInto;
$args.get(stringify!($param_name)).clone().try_into()?
})
}
);
};
// optional named arguments
(
Named { $export:tt $args:ident $body:block }
Positional { $($positional_count:tt)* }
Rest { , -- $param_name:ident ? : $param_kind:tt $($rest:tt)* }
CommandConfig {
name: $config_name:tt,
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
optional_positional: vec![ $($optional_positional:tt)* ],
rest_positional: $rest_positional:tt,
named: {
$($config_named:tt)*
}
}
Function {
$($function:tt)*
}
Extract {
$($extract:tt)*
}
) => {
command!(
Named { $export $args $body }
Positional { $($positional_count)* + 1 }
Rest { $($rest)* }
CommandConfig {
name: $config_name,
mandatory_positional: vec![ $($mandatory_positional)* ],
optional_positional: vec![ $($optional_positional)* ],
rest_positional: $rest_positional,
named: {
$($config_named)*
($param_name : Optional(NamedValue::Single))
}
}
Function {
$($function)* ($param_name : $param_kind)
}
Extract {
($($extract)* {
use std::convert::TryInto;
$args.get(stringify!($param_name)).clone().try_into()?
})
}
);
};
// mandatory positional argument
(
Named { $export:ident $args:ident $body:block }
Positional { $($positional_count:tt)* }
Rest { , $param_name:ident : $param_kind:tt $($rest:tt)* }
CommandConfig {
name: $config_name:tt,
mandatory_positional: vec![ $($mandatory_positional:tt)* ],
optional_positional: vec![ $($optional_positional:tt)* ],
rest_positional: $rest_positional:tt,
named: {
$($config_named:tt)*
}
}
Function {
$($function:tt)*
}
Extract {
$($extract:tt)*
}
) => {
command!(
Named { $export $args $body }
Positional { $($positional_count)* + 1 }
Rest { $($rest)* }
CommandConfig {
name: $config_name,
mandatory_positional: vec![ $($mandatory_positional)* $crate::parser::registry::PositionalType::mandatory(
stringify!($param_name), stringify!($param_kind)
), ],
optional_positional: vec![ $($optional_positional)* ],
rest_positional: $rest_positional,
named: {
$($config_named)*
}
}
Function {
$($function)* ($param_name : $param_kind)
}
Extract {
$($extract:tt)* {
use std::convert::TryInto;
$args.expect_nth($($positional_count)*)?.try_into()?
}
}
);
};
($export:ident as $config_name:tt ( $args:ident $($command_rest:tt)* ) $body:block) => {
command!(
Named { $export $args $body }
Positional { 0 }
Rest { $($command_rest)* }
CommandConfig {
name: $config_name,
mandatory_positional: vec![],
optional_positional: vec![],
rest_positional: false,
named: {}
}
Function {
}
Extract {
}
);
};
// ($export:ident as $name:tt ( $args:ident, -- $param:ident : $kind:ident ) $body:block) => {
// #[allow(non_camel_case_types)]
// pub struct $export;
// impl Command for $export {
// fn run(&self, $args: CommandArgs) -> Result<OutputStream, ShellError> {
// fn command($args: CommandArgs, $param: $kind) -> Result<OutputStream, ShellError> {
// $body
// }
// use std::convert::TryInto;
// let param = $args.get(stringify!($param)).try_into()?;
// command($args, param)
// }
// fn name(&self) -> &str {
// stringify!($name)
// }
// fn config(&self) -> CommandConfig {
// let mut named: IndexMap<String, NamedType> = IndexMap::new();
// named.insert(stringify!($param).to_string(), NamedType::$kind);
// CommandConfig {
// name: self.name().to_string(),
// mandatory_positional: vec![],
// optional_positional: vec![],
// rest_positional: false,
// named,
// }
// }
// }
// };
}

View File

@ -1,38 +1,74 @@
use crate::errors::ShellError;
use crate::object::{Primitive, Value};
use crate::object::{Primitive, Switch, Value};
use crate::parser::parse::span::Span;
use crate::parser::registry::{CommandConfig, NamedType};
use crate::parser::registry::NamedType;
use crate::prelude::*;
use indexmap::IndexMap;
use mime::Mime;
use std::path::{Path, PathBuf};
use std::str::FromStr;
pub struct Open;
command! {
Open as open(args, --raw: Switch) {
let span = args.name_span;
impl Command for Open {
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
open(args)
}
fn name(&self) -> &str {
"open"
}
let cwd = args
.env
.lock()
.unwrap()
.front()
.unwrap()
.path()
.to_path_buf();
fn config(&self) -> CommandConfig {
let mut named: IndexMap<String, NamedType> = IndexMap::new();
named.insert("raw".to_string(), NamedType::Switch);
let full_path = PathBuf::from(cwd);
CommandConfig {
name: self.name().to_string(),
mandatory_positional: vec![],
optional_positional: vec![],
rest_positional: false,
named,
is_filter: true,
is_sink: false,
can_load: vec![],
can_save: vec![],
}
let (file_extension, contents) = match &args.expect_nth(0)?.item {
Value::Primitive(Primitive::String(s)) => fetch(&full_path, s, args.expect_nth(0)?.span)?,
_ => {
return Err(ShellError::labeled_error(
"Expected string value for filename",
"expected filename",
args.expect_nth(0)?.span,
));
}
};
let mut stream = VecDeque::new();
let file_extension = if raw.is_present() {
None
} else if args.has("json") {
Some("json".to_string())
} else if args.has("xml") {
Some("xml".to_string())
} else if args.has("ini") {
Some("ini".to_string())
} else if args.has("yaml") {
Some("yaml".to_string())
} else if args.has("toml") {
Some("toml".to_string())
} else {
if let Some(ref named_args) = args.args.named {
for named in named_args.iter() {
return Err(ShellError::labeled_error(
"Unknown flag for open",
"unknown flag",
named.1.span.clone(),
));
}
file_extension
} else {
file_extension
}
};
stream.push_back(ReturnSuccess::value(parse_as_value(
file_extension,
contents,
span,
)?));
stream
}
}
@ -40,7 +76,7 @@ pub fn fetch(
cwd: &PathBuf,
location: &str,
span: Span,
) -> Result<(Option<String>, Value), ShellError> {
) -> Result<(Option<String>, String), ShellError> {
let mut cwd = cwd.clone();
if location.starts_with("http:") || location.starts_with("https:") {
let response = reqwest::get(location);
@ -168,74 +204,3 @@ pub fn parse_as_value(
_ => Ok(Value::string(contents)),
}
}
fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.len() == 0 {
return Err(ShellError::maybe_labeled_error(
"Open requires a path or url",
"needs path or url",
args.name_span,
));
}
let span = args.name_span;
let cwd = args
.env
.lock()
.unwrap()
.front()
.unwrap()
.path()
.to_path_buf();
let full_path = PathBuf::from(cwd);
let (file_extension, contents) = match &args.expect_nth(0)?.item {
Value::Primitive(Primitive::String(s)) => fetch(&full_path, s, args.expect_nth(0)?.span)?,
_ => {
return Err(ShellError::labeled_error(
"Expected string value for filename",
"expected filename",
args.expect_nth(0)?.span,
));
}
};
let mut stream = VecDeque::new();
let file_extension = if args.has("raw") {
None
} else if args.has("json") {
Some("json".to_string())
} else if args.has("xml") {
Some("xml".to_string())
} else if args.has("ini") {
Some("ini".to_string())
} else if args.has("yaml") {
Some("yaml".to_string())
} else if args.has("toml") {
Some("toml".to_string())
} else {
if let Some(ref named_args) = args.args.named {
for named in named_args.iter() {
return Err(ShellError::labeled_error(
"Unknown flag for open",
"unknown flag",
named.1.span.clone(),
));
}
file_extension
} else {
file_extension
}
};
match contents {
Value::Primitive(Primitive::String(x)) => {
stream.push_back(ReturnValue::Value(parse_as_value(file_extension, x, span)?));
}
x => stream.push_back(ReturnValue::Value(x)),
}
Ok(stream.boxed())
}

View File

@ -17,9 +17,8 @@ pub fn pick(args: CommandArgs) -> Result<OutputStream, ShellError> {
let objects = args
.input
.map(move |item| Value::Object(select_fields(&item, &fields)))
.map(|item| ReturnValue::Value(item));
.values
.map(move |item| Value::Object(select_fields(&item, &fields)));
let stream = Pin::new(Box::new(objects));
Ok(stream)
Ok(objects.from_input_stream())
}

View File

@ -93,11 +93,9 @@ pub fn filter_plugin(path: String, args: CommandArgs) -> Result<OutputStream, Sh
},
Err(e) => {
let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new(
ShellError::string(format!(
"Error while processing input: {:?} {}",
e, input
)),
result.push_back(Err(ShellError::string(format!(
"Error while processing input: {:?} {}",
e, input
))));
result
}
@ -105,8 +103,9 @@ pub fn filter_plugin(path: String, args: CommandArgs) -> Result<OutputStream, Sh
}
Err(e) => {
let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new(
ShellError::string(format!("Error while processing input: {:?}", e)),
result.push_back(Err(ShellError::string(format!(
"Error while processing input: {:?}",
e
))));
result
}
@ -115,7 +114,7 @@ pub fn filter_plugin(path: String, args: CommandArgs) -> Result<OutputStream, Sh
})
.flatten();
Ok(stream.boxed())
Ok(stream.to_output_stream())
}
pub fn sink_plugin(path: String, args: SinkCommandArgs) -> Result<(), ShellError> {

View File

@ -11,8 +11,8 @@ pub fn ps(_args: CommandArgs) -> Result<OutputStream, ShellError> {
let list = list
.into_iter()
.map(|(_, process)| ReturnValue::Value(Value::Object(process_dict(process))))
.map(|(_, process)| Value::Object(process_dict(process)))
.collect::<VecDeque<_>>();
Ok(list.boxed())
Ok(list.from_input_stream())
}

View File

@ -17,8 +17,8 @@ pub fn reject(args: CommandArgs) -> Result<OutputStream, ShellError> {
let stream = args
.input
.map(move |item| Value::Object(reject_fields(&item, &fields)))
.map(|item| ReturnValue::Value(item));
.values
.map(move |item| Value::Object(reject_fields(&item, &fields)));
Ok(stream.boxed())
Ok(stream.from_input_stream())
}

View File

@ -34,7 +34,7 @@ pub fn size(args: CommandArgs) -> Result<OutputStream, ShellError> {
contents.clear();
}
Ok(list.boxed())
Ok(list.to_output_stream())
}
fn count(name: &str, contents: &str) -> ReturnValue {
@ -69,5 +69,5 @@ fn count(name: &str, contents: &str) -> ReturnValue {
dict.add("chars", Value::int(chars));
dict.add("max length", Value::int(bytes));
ReturnValue::Value(Value::Object(dict))
ReturnSuccess::value(Value::Object(dict))
}

View File

@ -16,8 +16,7 @@ impl Command for SkipWhile {
fn config(&self) -> CommandConfig {
CommandConfig {
name: self.name().to_string(),
mandatory_positional: vec![PositionalType::Block("condition".to_string())],
optional_positional: vec![],
positional: vec![PositionalType::mandatory("condition", "Block")],
rest_positional: false,
named: indexmap::IndexMap::new(),
is_filter: true,
@ -40,7 +39,7 @@ pub fn skip_while(args: CommandArgs) -> Result<OutputStream, ShellError> {
let block = args.nth(0).unwrap().as_block()?;
let input = args.input;
let objects = input.skip_while(move |item| {
let objects = input.values.skip_while(move |item| {
let result = block.invoke(&item);
let return_value = match result {
@ -51,5 +50,5 @@ pub fn skip_while(args: CommandArgs) -> Result<OutputStream, ShellError> {
futures::future::ready(return_value)
});
Ok(objects.map(|x| ReturnValue::Value(x)).boxed())
Ok(objects.from_input_stream())
}

View File

@ -5,7 +5,7 @@ pub fn sort_by(args: CommandArgs) -> Result<OutputStream, ShellError> {
let fields: Result<Vec<_>, _> = args.positional_iter().map(|a| a.as_string()).collect();
let fields = fields?;
let output = args.input.collect::<Vec<_>>();
let output = args.input.values.collect::<Vec<_>>();
let output = output.map(move |mut vec| {
vec.sort_by_key(|item| {
@ -15,11 +15,8 @@ pub fn sort_by(args: CommandArgs) -> Result<OutputStream, ShellError> {
.collect::<Vec<Option<Value>>>()
});
vec.into_iter()
.map(|v| ReturnValue::Value(v.copy()))
.collect::<VecDeque<_>>()
.boxed()
vec.into_iter().collect::<VecDeque<_>>()
});
Ok(output.flatten_stream().boxed())
Ok(output.flatten_stream().from_input_stream())
}

View File

@ -8,7 +8,7 @@ use log::trace;
pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
let positional: Vec<_> = args.positional_iter().cloned().collect();
let span = args.name_span;
if positional.len() == 0 {
return Err(ShellError::maybe_labeled_error(
"Split-column needs more information",
@ -20,6 +20,7 @@ pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
let input = args.input;
Ok(input
.values
.map(move |v| match v {
Value::Primitive(Primitive::String(s)) => {
let splitter = positional[0].as_string().unwrap().replace("\\n", "\n");
@ -39,7 +40,7 @@ pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
for (&k, v) in split_result.iter().zip(gen_columns.iter()) {
dict.add(v.clone(), Value::Primitive(Primitive::String(k.into())));
}
ReturnValue::Value(Value::Object(dict))
ReturnSuccess::value(Value::Object(dict))
} else if split_result.len() == (positional.len() - 1) {
let mut dict = crate::object::Dictionary::default();
for (&k, v) in split_result.iter().zip(positional.iter().skip(1)) {
@ -48,7 +49,7 @@ pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
Value::Primitive(Primitive::String(k.into())),
);
}
ReturnValue::Value(Value::Object(dict))
ReturnSuccess::value(Value::Object(dict))
} else {
let mut dict = crate::object::Dictionary::default();
for k in positional.iter().skip(1) {
@ -57,14 +58,14 @@ pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
Value::Primitive(Primitive::String("".into())),
);
}
ReturnValue::Value(Value::Object(dict))
ReturnSuccess::value(Value::Object(dict))
}
}
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -21,6 +21,7 @@ pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
let input = args.input;
let stream = input
.values
.map(move |v| match v {
Value::Primitive(Primitive::String(s)) => {
let splitter = positional[0].as_string().unwrap().replace("\\n", "\n");
@ -31,7 +32,7 @@ pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut result = VecDeque::new();
for s in split_result {
result.push_back(ReturnValue::Value(Value::Primitive(Primitive::String(
result.push_back(ReturnSuccess::value(Value::Primitive(Primitive::String(
s.into(),
))));
}
@ -39,17 +40,15 @@ pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
}
_ => {
let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new(
ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
),
))));
result.push_back(Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)));
result
}
})
.flatten();
Ok(stream.boxed())
Ok(stream.to_output_stream())
}

View File

@ -196,7 +196,7 @@ pub fn sysinfo(_args: CommandArgs) -> Result<OutputStream, ShellError> {
// println!("{:#?}", system.get_network());
let mut stream = VecDeque::new();
stream.push_back(ReturnValue::Value(Value::Object(Dictionary::from(idx))));
stream.push_back(Value::Object(Dictionary::from(idx)));
Ok(stream.boxed())
Ok(stream.from_input_stream())
}

View File

@ -2,9 +2,10 @@ use crate::object::Value;
use crate::prelude::*;
pub fn to_array(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input.collect();
let out = args.input.values.collect();
Ok(out
.map(|vec: Vec<_>| single_output(Value::List(vec)))
.map(|vec: Vec<_>| stream![Value::List(vec)])
.flatten_stream()
.boxed())
.from_input_stream())
}

View File

@ -47,14 +47,12 @@ pub fn to_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(out
.map(
move |a| match serde_json::to_string(&value_to_json_value(&a)) {
Ok(x) => ReturnValue::Value(Value::Primitive(Primitive::String(x))),
Err(_) => {
ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
"Can not convert to JSON string",
"can not convert piped data to JSON string",
span,
))))
}
Ok(x) => Ok(ReturnValue::Value(Value::Primitive(Primitive::String(x)))),
Err(_) => Err(ShellError::maybe_labeled_error(
"Can not convert to JSON string",
"can not convert piped data to JSON string",
span,
)),
},
)
.boxed())

View File

@ -35,13 +35,14 @@ pub fn to_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input;
let span = args.name_span;
Ok(out
.map(move |a| match toml::to_string(&value_to_toml_value(&a)) {
Ok(x) => ReturnValue::Value(Value::Primitive(Primitive::String(x))),
Err(_) => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
.values
.map(move |a| match toml::to_string(&a) {
Ok(x) => ReturnSuccess::value(Value::Primitive(Primitive::String(x))),
Err(_) => Err(ShellError::maybe_labeled_error(
"Can not convert to TOML string",
"can not convert piped data to TOML string",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -9,15 +9,16 @@ pub fn trim(args: CommandArgs) -> Result<OutputStream, ShellError> {
let span = args.name_span;
Ok(input
.values
.map(move |v| match v {
Value::Primitive(Primitive::String(s)) => {
ReturnValue::Value(Value::Primitive(Primitive::String(s.trim().into())))
ReturnSuccess::value(Value::Primitive(Primitive::String(s.trim().into())))
}
_ => ReturnValue::Value(Value::Error(Box::new(ShellError::maybe_labeled_error(
_ => Err(ShellError::maybe_labeled_error(
"Expected string values from pipeline",
"expects strings from pipeline",
span,
)))),
)),
})
.boxed())
.to_output_stream())
}

View File

@ -46,5 +46,5 @@ pub fn view(args: CommandArgs) -> Result<OutputStream, ShellError> {
let _ = printer.file(file.display().to_string());
Ok(VecDeque::new().boxed())
Ok(VecDeque::new().into())
}

View File

@ -1,55 +1,23 @@
use crate::errors::ShellError;
use crate::parser::registry::{CommandConfig, PositionalType};
use crate::object::Block;
use crate::prelude::*;
use futures::future::ready;
use log::trace;
pub struct Where;
command! {
Where as where(args, condition: Block) {
let input: InputStream = trace_stream!("where input" = args.input);
impl Command for Where {
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
r#where(args)
}
fn name(&self) -> &str {
"where"
}
input.values.filter_map(move |item| {
let result = condition.invoke(&item);
fn config(&self) -> CommandConfig {
CommandConfig {
name: self.name().to_string(),
mandatory_positional: vec![PositionalType::Block("condition".to_string())],
optional_positional: vec![],
rest_positional: false,
named: indexmap::IndexMap::new(),
is_filter: true,
is_sink: false,
can_load: vec![],
can_save: vec![],
}
let return_value = match result {
Err(err) => Some(Err(err)),
Ok(v) if v.is_true() => Some(Ok(ReturnSuccess::Value(item.copy()))),
_ => None,
};
ready(return_value)
})
}
}
pub fn r#where(args: CommandArgs) -> Result<OutputStream, ShellError> {
if args.len() == 0 {
return Err(ShellError::maybe_labeled_error(
"Where requires a condition",
"needs condition",
args.name_span,
));
}
let block = args.expect_nth(0)?.as_block()?;
let input = args.input;
let objects = input.filter_map(move |item| {
let result = block.invoke(&item);
let return_value = match result {
Err(err) => Some(ReturnValue::Value(Value::Error(Box::new(err)))),
Ok(v) if v.is_true() => Some(ReturnValue::Value(item.copy())),
_ => None,
};
futures::future::ready(return_value)
});
Ok(objects.boxed())
}

View File

@ -41,6 +41,16 @@ pub enum ArgumentError {
MissingValueForName(String),
}
pub fn labelled(
span: impl Into<Option<Span>>,
heading: &'a str,
span_message: &'a str,
) -> impl FnOnce(ShellError) -> ShellError + 'a {
let span = span.into();
move |error| ShellError::maybe_labeled_error(heading, span_message, span)
}
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
pub enum ShellError {
String(StringError),
@ -53,6 +63,7 @@ pub enum ShellError {
expr: Description,
},
ArgumentError {
command: String,
error: ArgumentError,
span: Span,
},
@ -119,28 +130,38 @@ impl ShellError {
ShellError::String(StringError { title, .. }) => {
Diagnostic::new(Severity::Error, title)
}
ShellError::ArgumentError { error, span } => match error {
ShellError::ArgumentError {
command,
error,
span,
} => match error {
ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new(
Severity::Error,
format!(
"Command requires {}{}",
Color::Cyan.paint("--"),
Color::Cyan.paint(name)
"{} requires {}{}",
Color::Cyan.paint(command),
Color::Black.bold().paint("--"),
Color::Black.bold().paint(name)
),
)
.with_label(Label::new_primary(span)),
ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new(
Severity::Error,
format!("Command requires {}", Color::Cyan.paint(name)),
format!(
"{} requires {}",
Color::Cyan.paint(command),
Color::Green.bold().paint(name)
),
)
.with_label(Label::new_primary(span)),
ArgumentError::MissingValueForName(name) => Diagnostic::new(
Severity::Error,
format!(
"Missing value for flag {}{}",
Color::Cyan.paint("--"),
Color::Cyan.paint(name)
"{} is missing value for flag {}{}",
Color::Cyan.paint(command),
Color::Black.bold().paint("--"),
Color::Black.bold().paint(name)
),
)
.with_label(Label::new_primary(span)),

View File

@ -31,7 +31,6 @@ impl TreeView {
}
}
Value::Block(_) => {}
Value::Error(_) => {}
Value::Filesystem => {}
Value::Binary(_) => {}
}

View File

@ -4,6 +4,10 @@
#![feature(try_trait)]
#![feature(bind_by_move_pattern_guards)]
#![feature(box_syntax)]
#![feature(type_ascription)]
#[macro_use]
mod prelude;
mod cli;
mod commands;
@ -16,11 +20,10 @@ mod git;
mod object;
mod parser;
mod plugin;
mod prelude;
mod shell;
mod stream;
pub use crate::commands::command::ReturnValue;
pub use crate::commands::command::{ReturnSuccess, ReturnValue};
pub use crate::env::host::BasicHost;
pub use crate::parser::parse::span::SpannedItem;
pub use crate::parser::Spanned;

View File

@ -185,9 +185,6 @@ pub enum Value {
#[allow(unused)]
Block(Block),
Filesystem,
#[allow(unused)]
Error(Box<ShellError>),
}
pub fn debug_list(values: &'a Vec<Value>) -> ValuesDebug<'a> {
@ -217,7 +214,6 @@ impl fmt::Debug for ValueDebug<'a> {
Value::Object(o) => o.debug(f),
Value::List(l) => debug_list(l).fmt(f),
Value::Block(_) => write!(f, "[[block]]"),
Value::Error(err) => write!(f, "[[error :: {} ]]", err),
Value::Filesystem => write!(f, "[[filesystem]]"),
Value::Binary(_) => write!(f, "[[binary]]"),
}
@ -231,6 +227,65 @@ impl Spanned<Value> {
}
}
impl std::convert::TryFrom<&'a Spanned<Value>> for Block {
type Error = ShellError;
fn try_from(value: &'a Spanned<Value>) -> Result<Block, ShellError> {
match value.item() {
Value::Block(block) => Ok(block.clone()),
v => Err(ShellError::type_error(
"Block",
value.copy_span(v.type_name()),
)),
}
}
}
impl std::convert::TryFrom<&'a Spanned<Value>> for i64 {
type Error = ShellError;
fn try_from(value: &'a Spanned<Value>) -> Result<i64, ShellError> {
match value.item() {
Value::Primitive(Primitive::Int(int)) => Ok(*int),
v => Err(ShellError::type_error(
"Integer",
value.copy_span(v.type_name()),
)),
}
}
}
pub enum Switch {
Present,
Absent,
}
impl Switch {
pub fn is_present(&self) -> bool {
match self {
Switch::Present => true,
Switch::Absent => false,
}
}
}
impl std::convert::TryFrom<Option<&'a Spanned<Value>>> for Switch {
type Error = ShellError;
fn try_from(value: Option<&'a Spanned<Value>>) -> Result<Switch, ShellError> {
match value {
None => Ok(Switch::Absent),
Some(value) => match value.item() {
Value::Primitive(Primitive::Boolean(true)) => Ok(Switch::Present),
v => Err(ShellError::type_error(
"Boolean",
value.copy_span(v.type_name()),
)),
},
}
}
}
impl Value {
crate fn type_name(&self) -> String {
match self {
@ -238,7 +293,6 @@ impl Value {
Value::Object(_) => format!("object"),
Value::List(_) => format!("list"),
Value::Block(_) => format!("block"),
Value::Error(_) => format!("error"),
Value::Filesystem => format!("filesystem"),
Value::Binary(_) => format!("binary"),
}
@ -312,7 +366,6 @@ impl Value {
let list = l.iter().map(|i| i.copy()).collect();
Value::List(list)
}
Value::Error(e) => Value::Error(Box::new(e.copy_error())),
Value::Filesystem => Value::Filesystem,
Value::Binary(b) => Value::Binary(b.clone()),
}
@ -329,7 +382,6 @@ impl Value {
),
Value::Object(_) => format!("[object Object]"),
Value::List(_) => format!("[list List]"),
Value::Error(e) => format!("{}", e),
Value::Filesystem => format!("<filesystem>"),
Value::Binary(_) => format!("<binary>"),
}
@ -459,14 +511,6 @@ impl Value {
Ok(Value::Primitive(Primitive::Date(date)))
}
#[allow(unused)]
pub fn system_date_result(s: Result<SystemTime, std::io::Error>) -> Value {
match s {
Ok(time) => Value::Primitive(Primitive::Date(time.into())),
Err(err) => Value::Error(Box::new(ShellError::string(format!("{}", err)))),
}
}
pub fn nothing() -> Value {
Value::Primitive(Primitive::Nothing)
}

View File

@ -23,7 +23,7 @@ impl language_reporting::ReportingFiles for Files {
0
}
fn file_name(&self, _file: Self::FileId) -> FileName {
FileName::Verbatim(format!("<eval>"))
FileName::Verbatim(format!("shell"))
}
fn byte_index(&self, _file: Self::FileId, _line: usize, _column: usize) -> Option<usize> {
unimplemented!("byte_index")

View File

@ -1,5 +1,5 @@
use crate::errors::{ArgumentError, ShellError};
use crate::parser::registry::{CommandConfig, CommandRegistry, NamedType};
use crate::parser::registry::{CommandConfig, CommandRegistry, NamedType, PositionalType};
use crate::parser::{baseline_parse_tokens, CallNode, Span, Spanned};
use crate::parser::{
hir::{self, NamedArguments},
@ -86,30 +86,32 @@ fn parse_command_tail(
named.insert_switch(name, flag);
}
NamedType::Mandatory(kind) => match extract_mandatory(name, tail, source, command_span)
{
Err(err) => return Err(err), // produce a correct diagnostic
Ok((pos, flag)) => {
tail.move_to(pos);
NamedType::Mandatory(kind) => {
match extract_mandatory(config, name, tail, source, command_span) {
Err(err) => return Err(err), // produce a correct diagnostic
Ok((pos, flag)) => {
tail.move_to(pos);
if tail.at_end() {
return Err(ShellError::ArgumentError {
error: ArgumentError::MissingValueForName(name.to_string()),
span: flag.span,
});
if tail.at_end() {
return Err(ShellError::ArgumentError {
command: config.name().clone(),
error: ArgumentError::MissingValueForName(name.to_string()),
span: flag.span,
});
}
let expr = hir::baseline_parse_next_expr(
tail,
registry,
source,
kind.to_coerce_hint(),
)?;
tail.restart();
named.insert_mandatory(name, expr);
}
let expr = hir::baseline_parse_next_expr(
tail,
registry,
source,
kind.to_coerce_hint(),
)?;
tail.restart();
named.insert_mandatory(name, expr);
}
},
}
NamedType::Optional(kind) => match extract_optional(name, tail, source) {
Err(err) => return Err(err), // produce a correct diagnostic
Ok(Some((pos, flag))) => {
@ -117,6 +119,7 @@ fn parse_command_tail(
if tail.at_end() {
return Err(ShellError::ArgumentError {
command: config.name().clone(),
error: ArgumentError::MissingValueForName(name.to_string()),
span: flag.span,
});
@ -144,16 +147,26 @@ fn parse_command_tail(
trace_remaining("after named", tail.clone(), source);
let mut positional = vec![];
let mandatory = config.mandatory_positional();
for arg in mandatory {
trace!("Processing mandatory {:?}", arg);
for arg in config.positional() {
trace!("Processing positional {:?}", arg);
if tail.len() == 0 {
return Err(ShellError::ArgumentError {
error: ArgumentError::MissingMandatoryPositional(arg.name().to_string()),
span: command_span,
});
match arg {
PositionalType::Mandatory(..) => {
if tail.len() == 0 {
return Err(ShellError::ArgumentError {
command: config.name().clone(),
error: ArgumentError::MissingMandatoryPositional(arg.name().to_string()),
span: command_span,
});
}
}
PositionalType::Optional(..) => {
if tail.len() == 0 {
break;
}
}
}
let result = hir::baseline_parse_next_expr(tail, registry, source, arg.to_coerce_hint())?;
@ -161,21 +174,7 @@ fn parse_command_tail(
positional.push(result);
}
trace_remaining("after mandatory", tail.clone(), source);
let optional = config.optional_positional();
for arg in optional {
if tail.len() == 0 {
break;
}
let result = hir::baseline_parse_next_expr(tail, registry, source, arg.to_coerce_hint())?;
positional.push(result);
}
trace_remaining("after optional", tail.clone(), source);
trace_remaining("after positional", tail.clone(), source);
// TODO: Only do this if rest params are specified
let remainder = baseline_parse_tokens(tail, registry, source)?;
@ -207,6 +206,7 @@ fn extract_switch(name: &str, tokens: &mut hir::TokensIterator<'_>, source: &Tex
}
fn extract_mandatory(
config: &CommandConfig,
name: &str,
tokens: &mut hir::TokensIterator<'a>,
source: &Text,
@ -216,6 +216,7 @@ fn extract_mandatory(
match flag {
None => Err(ShellError::ArgumentError {
command: config.name().clone(),
error: ArgumentError::MissingMandatoryFlag(name.to_string()),
span,
}),

View File

@ -36,22 +36,38 @@ impl NamedValue {
#[allow(unused)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PositionalType {
Value(String),
Block(String),
Mandatory(String, PositionalValue),
Optional(String, PositionalValue),
}
#[derive(Debug, Clone)]
pub enum PositionalValue {
Value,
Block,
}
impl PositionalType {
crate fn mandatory(name: &str, kind: &str) -> PositionalType {
match kind {
"Block" => PositionalType::Mandatory(name.to_string(), PositionalValue::Block),
_ => PositionalType::Mandatory(name.to_string(), PositionalValue::Value),
}
}
crate fn to_coerce_hint(&self) -> Option<ExpressionKindHint> {
match self {
PositionalType::Value(_) => None,
PositionalType::Block(_) => Some(ExpressionKindHint::Block),
PositionalType::Mandatory(_, PositionalValue::Block)
| PositionalType::Optional(_, PositionalValue::Block) => {
Some(ExpressionKindHint::Block)
}
_ => None,
}
}
crate fn name(&self) -> &str {
match self {
PositionalType::Value(s) => s,
PositionalType::Block(s) => s,
PositionalType::Mandatory(s, _) => s,
PositionalType::Optional(s, _) => s,
}
}
}
@ -60,8 +76,7 @@ impl PositionalType {
#[get = "crate"]
pub struct CommandConfig {
pub name: String,
pub mandatory_positional: Vec<PositionalType>,
pub optional_positional: Vec<PositionalType>,
crate positional: Vec<PositionalType>,
pub rest_positional: bool,
pub named: IndexMap<String, NamedType>,
pub is_filter: bool,

View File

@ -3,6 +3,9 @@ use nu::{
serve_plugin, Args, CommandConfig, Plugin, PositionalType, Primitive, ReturnValue, ShellError,
Spanned, Value,
};
use nu::{Primitive, ReturnSuccess, ReturnValue, ShellError, Spanned, Value};
use serde::{Deserialize, Serialize};
use std::io;
struct Inc {
inc_by: i64,
@ -13,42 +16,75 @@ impl Inc {
}
}
impl Plugin for Inc {
fn config(&mut self) -> Result<CommandConfig, ShellError> {
Ok(CommandConfig {
name: "inc".to_string(),
mandatory_positional: vec![],
optional_positional: vec![PositionalType::Value("Increment".into())],
can_load: vec![],
can_save: vec![],
is_filter: true,
is_sink: false,
named: IndexMap::new(),
rest_positional: true,
})
}
fn begin_filter(&mut self, args: Args) -> Result<(), ShellError> {
if let Some(args) = args.positional {
for arg in args {
match arg {
Spanned {
item: Value::Primitive(Primitive::Int(i)),
..
} => {
self.inc_by = i;
fn send_response<T: Serialize>(result: Vec<T>) {
let response = JsonRpc::new("response", result);
let response_raw = serde_json::to_string(&response).unwrap();
println!("{}", response_raw);
}
fn send_error(error: ShellError) {
let message: ReturnValue = Err(error);
send_response(vec![message])
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "method")]
#[allow(non_camel_case_types)]
pub enum NuCommand {
init { params: Vec<Spanned<Value>> },
filter { params: Value },
quit,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut inc_by = 1;
loop {
let mut input = String::new();
match io::stdin().read_line(&mut input) {
Ok(_) => {
let command = serde_json::from_str::<NuCommand>(&input);
match command {
Ok(NuCommand::init { params }) => {
for param in params {
match param {
Spanned {
item: Value::Primitive(Primitive::Int(i)),
..
} => {
inc_by = i;
}
_ => {
send_error(ShellError::string("Unrecognized type in params"));
}
}
}
}
Ok(NuCommand::filter { params }) => match params {
Value::Primitive(Primitive::Int(i)) => {
send_response(vec![ReturnSuccess::value(Value::int(i + inc_by))]);
}
Value::Primitive(Primitive::Bytes(b)) => {
send_response(vec![ReturnSuccess::value(Value::bytes(
b + inc_by as u128,
))]);
}
_ => {
send_error(ShellError::string("Unrecognized type in stream"));
}
},
Ok(NuCommand::quit) => {
break;
}
Err(_) => {
send_error(ShellError::string("Unrecognized type in stream"));
}
_ => return Err(ShellError::string("Unrecognized type in params")),
}
}
}
Ok(())
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
match input {
Value::Primitive(Primitive::Int(i)) => {
Ok(vec![ReturnValue::Value(Value::int(i + self.inc_by))])
Err(_) => {
send_error(ShellError::string("Unrecognized type in stream"));
}
Value::Primitive(Primitive::Bytes(b)) => Ok(vec![ReturnValue::Value(Value::bytes(
b + self.inc_by as u64,

View File

@ -1,13 +1,82 @@
#[macro_export]
macro_rules! stream {
($($expr:expr),*) => {{
let mut v = VecDeque::new();
$(
v.push_back($expr);
)*
v
}}
}
#[macro_export]
macro_rules! trace_stream {
($desc:tt = $expr:expr) => {{
if log::log_enabled!(target: "nu::trace_stream", log::Level::Trace) {
use futures::stream::StreamExt;
// Blocking is generally quite bad, but this is for debugging
// let mut local = futures::executor::LocalPool::new();
// let objects = local.run_until($expr.into_vec());
// let objects: Vec<_> = futures::executor::block_on($expr.into_vec());
let objects = $expr.values.inspect(|o| {
trace!(target: "nu::trace_stream", "{} = {:#?}", $desc, o.debug());
});
$crate::stream::InputStream::from_stream(objects.boxed())
} else {
$expr
}
}};
}
crate use crate::cli::MaybeOwned;
crate use crate::commands::command::{Command, CommandAction, CommandArgs, ReturnValue};
crate use crate::commands::command::{
Command, CommandAction, CommandArgs, ReturnSuccess, ReturnValue,
};
crate use crate::context::Context;
crate use crate::env::host::handle_unexpected;
crate use crate::env::{Environment, Host};
crate use crate::errors::ShellError;
crate use crate::object::{Primitive, Value};
crate use crate::stream::{single_output, InputStream, OutputStream};
crate use crate::stream::{InputStream, OutputStream};
crate use crate::Text;
crate use futures::stream::BoxStream;
crate use futures::Stream;
crate use futures::{FutureExt, StreamExt};
crate use std::collections::VecDeque;
crate use std::pin::Pin;
crate use std::future::Future;
crate use std::sync::{Arc, Mutex};
pub trait FromInputStream {
fn from_input_stream(self) -> OutputStream;
}
impl<T> FromInputStream for T
where
T: Stream<Item = Value> + Send + 'static,
{
fn from_input_stream(self) -> OutputStream {
OutputStream {
values: self.map(ReturnSuccess::value).boxed(),
}
}
}
pub trait ToOutputStream {
fn to_output_stream(self) -> OutputStream;
}
impl<T, U> ToOutputStream for T
where
T: Stream<Item = U> + Send + 'static,
U: Into<ReturnValue>,
{
fn to_output_stream(self) -> OutputStream {
OutputStream {
values: self.map(|item| item.into()).boxed(),
}
}
}

View File

@ -1,12 +1,112 @@
use crate::prelude::*;
use futures::stream::BoxStream;
pub type InputStream = BoxStream<'static, Value>;
pub type OutputStream = BoxStream<'static, ReturnValue>;
crate fn single_output(item: Value) -> OutputStream {
let value = ReturnValue::Value(item);
let mut vec = VecDeque::new();
vec.push_back(value);
vec.boxed()
pub struct InputStream {
crate values: BoxStream<'static, Value>,
}
impl InputStream {
pub fn into_vec(self) -> impl Future<Output = Vec<Value>> {
self.values.collect()
}
pub fn from_stream(input: impl Stream<Item = Value> + Send + 'static) -> InputStream {
InputStream {
values: input.boxed(),
}
}
}
impl From<BoxStream<'static, Value>> for InputStream {
fn from(input: BoxStream<'static, Value>) -> InputStream {
InputStream { values: input }
}
}
impl From<VecDeque<Value>> for InputStream {
fn from(input: VecDeque<Value>) -> InputStream {
InputStream {
values: input.boxed(),
}
}
}
impl From<Vec<Value>> for InputStream {
fn from(input: Vec<Value>) -> InputStream {
let mut list = VecDeque::default();
list.extend(input);
InputStream {
values: list.boxed(),
}
}
}
pub struct OutputStream {
crate values: BoxStream<'static, ReturnValue>,
}
impl OutputStream {
pub fn from_stream(input: impl Stream<Item = ReturnValue> + Send + 'static) -> OutputStream {
OutputStream {
values: input.boxed(),
}
}
pub fn from_input(input: impl Stream<Item = Value> + Send + 'static) -> OutputStream {
OutputStream {
values: input.map(ReturnSuccess::value).boxed(),
}
}
}
impl From<InputStream> for OutputStream {
fn from(input: InputStream) -> OutputStream {
OutputStream {
values: input.values.map(ReturnSuccess::value).boxed(),
}
}
}
impl From<BoxStream<'static, Value>> for OutputStream {
fn from(input: BoxStream<'static, Value>) -> OutputStream {
OutputStream {
values: input.map(ReturnSuccess::value).boxed(),
}
}
}
impl From<BoxStream<'static, ReturnValue>> for OutputStream {
fn from(input: BoxStream<'static, ReturnValue>) -> OutputStream {
OutputStream { values: input }
}
}
impl From<VecDeque<ReturnValue>> for OutputStream {
fn from(input: VecDeque<ReturnValue>) -> OutputStream {
OutputStream {
values: input.boxed(),
}
}
}
impl From<Vec<ReturnValue>> for OutputStream {
fn from(input: Vec<ReturnValue>) -> OutputStream {
let mut list = VecDeque::default();
list.extend(input);
OutputStream {
values: list.boxed(),
}
}
}
impl From<Vec<Value>> for OutputStream {
fn from(input: Vec<Value>) -> OutputStream {
let mut list = VecDeque::default();
list.extend(input.into_iter().map(ReturnSuccess::value));
OutputStream {
values: list.boxed(),
}
}
}