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("sysinfo", Box::new(sysinfo::sysinfo)),
command("cd", Box::new(cd::cd)), command("cd", Box::new(cd::cd)),
command("view", Box::new(view::view)), command("view", Box::new(view::view)),
// command("skip", skip::Skip),
command("first", Box::new(first::first)), command("first", Box::new(first::first)),
command("size", Box::new(size::size)), command("size", Box::new(size::size)),
command("from-ini", Box::new(from_ini::from_ini)), 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-xml", Box::new(from_xml::from_xml)),
command("from-yaml", Box::new(from_yaml::from_yaml)), command("from-yaml", Box::new(from_yaml::from_yaml)),
command("get", Box::new(get::get)), command("get", Box::new(get::get)),
command("enter", Box::new(enter::enter)),
command("exit", Box::new(exit::exit)), command("exit", Box::new(exit::exit)),
command("lines", Box::new(lines::lines)), command("lines", Box::new(lines::lines)),
command("pick", Box::new(pick::pick)), 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-json", Box::new(to_json::to_json)),
command("to-toml", Box::new(to_toml::to_toml)), 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("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(Open),
Arc::new(Where), Arc::new(Where),
Arc::new(Config), Arc::new(Config),
Arc::new(SkipWhile), Arc::new(SkipWhile),
command("sort-by", Box::new(sort_by::sort_by)), Arc::new(Enter),
Arc::new(Skip),
]); ]);
context.add_sinks(vec![ context.add_sinks(vec![
@ -392,7 +396,7 @@ async fn process_line(readline: Result<String, ReadlineError>, ctx: &mut Context
} }
(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.into_vec().await;
if let Err(err) = left.run(ctx, input_vec) { if let Err(err) = left.run(ctx, input_vec) {
return LineResult::Error(line.clone(), err); return LineResult::Error(line.clone(), err);
} }

View File

@ -1,3 +1,6 @@
#[macro_use]
crate mod macros;
crate mod args; crate mod args;
crate mod autoview; crate mod autoview;
crate mod cd; crate mod cd;
@ -39,6 +42,8 @@ crate mod where_;
crate use command::command; crate use command::command;
crate use config::Config; crate use config::Config;
crate use enter::Enter;
crate use open::Open; crate use open::Open;
crate use skip::Skip;
crate use skip_while::SkipWhile; crate use skip_while::SkipWhile;
crate use where_::Where; 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)); stream.push_back(ReturnSuccess::change_cwd(path));
Ok(stream.boxed()) Ok(stream.into())
} }
_ => { _ => {
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
match args.nth(0) { match args.nth(0) {
None => { None => {
stream.push_back(ReturnValue::change_cwd(PathBuf::from("/"))); stream.push_back(ReturnSuccess::change_cwd(PathBuf::from("/")));
} }
Some(v) => { Some(v) => {
let mut cwd = latest.path().to_path_buf(); 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 { impl ClassifiedInputStream {
crate fn new() -> ClassifiedInputStream { crate fn new() -> ClassifiedInputStream {
ClassifiedInputStream { ClassifiedInputStream {
objects: VecDeque::new().boxed(), objects: VecDeque::new().into(),
stdin: None, 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 { 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, stdin: None,
} }
} }
crate fn from_stdout(stdout: std::fs::File) -> ClassifiedInputStream { crate fn from_stdout(stdout: std::fs::File) -> ClassifiedInputStream {
ClassifiedInputStream { ClassifiedInputStream {
objects: VecDeque::new().boxed(), objects: VecDeque::new().into(),
stdin: Some(stdout), stdin: Some(stdout),
} }
} }
@ -110,31 +128,23 @@ impl InternalCommand {
context: &mut Context, context: &mut Context,
input: ClassifiedInputStream, input: ClassifiedInputStream,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let objects = if log_enabled!(log::Level::Trace) { if log_enabled!(log::Level::Trace) {
trace!("->"); trace!("->");
trace!("{}", self.command.name()); trace!("{}", self.command.name());
trace!("{:?}", self.args.debug()); 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)?; context.run_command(self.command, self.name_span.clone(), self.args, objects)?;
let mut result = result.values;
let mut stream = VecDeque::new(); let mut stream = VecDeque::new();
while let Some(item) = result.next().await { while let Some(item) = result.next().await {
match item { match item? {
ReturnValue::Value(Value::Error(err)) => { ReturnSuccess::Action(action) => match action {
return Err(*err);
}
ReturnValue::Action(action) => match action {
CommandAction::ChangePath(path) => { CommandAction::ChangePath(path) => {
context.env.lock().unwrap().back_mut().map(|x| { context.env.lock().unwrap().back_mut().map(|x| {
x.path = path; x.path = path;
@ -158,12 +168,13 @@ impl InternalCommand {
}, },
}, },
ReturnValue::Value(v) => { ReturnSuccess::Value(v) => {
stream.push_back(v); stream.push_back(v);
} }
} }
} }
Ok(stream.boxed() as InputStream)
Ok(stream.into())
} }
} }
@ -187,9 +198,11 @@ impl ExternalCommand {
input: ClassifiedInputStream, input: ClassifiedInputStream,
stream_next: StreamNext, stream_next: StreamNext,
) -> Result<ClassifiedInputStream, ShellError> { ) -> 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); let mut arg_string = format!("{}", self.name);
for arg in &self.args { 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); process = process.stdin(stdin);
} }
@ -318,7 +331,9 @@ impl ExternalCommand {
let file = futures::io::AllowStdIo::new(stdout); let file = futures::io::AllowStdIo::new(stdout);
let stream = Framed::new(file, LinesCodec {}); let stream = Framed::new(file, LinesCodec {});
let stream = stream.map(|line| Value::string(line.unwrap())); 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::commands::command::SinkCommandArgs;
use crate::errors::ShellError; use crate::errors::{labelled, ShellError};
use clipboard::{ClipboardContext, ClipboardProvider}; use clipboard::{ClipboardContext, ClipboardProvider};
pub fn clip(args: SinkCommandArgs) -> Result<(), ShellError> { pub fn clip(args: SinkCommandArgs) -> Result<(), ShellError> {
let mut clip_context: ClipboardContext = ClipboardProvider::new().unwrap(); let mut clip_context: ClipboardContext = ClipboardProvider::new().unwrap();
let mut new_copy_data = String::new(); let mut new_copy_data = String::new();
if args.input.len() > 0 { if args.input.len() > 0 {
let mut first = true; let mut first = true;
for i in args.input.iter() { for i in args.input.iter() {
@ -13,18 +14,17 @@ pub fn clip(args: SinkCommandArgs) -> Result<(), ShellError> {
} else { } else {
first = false; first = false;
} }
match i.as_string() {
Ok(s) => new_copy_data.push_str(&s), let string = i.as_string().map_err(labelled(
Err(_) => { args.name_span,
return Err(ShellError::maybe_labeled_error(
"Given non-string data", "Given non-string data",
"expected strings from pipeline", "expected strings from pipeline",
args.name_span, ))?;
))
} new_copy_data.push_str(&string);
}
} }
} }
clip_context.set_contents(new_copy_data).unwrap(); clip_context.set_contents(new_copy_data).unwrap();
Ok(()) Ok(())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,22 +3,22 @@ use crate::object::Value;
use crate::parser::Span; use crate::parser::Span;
use crate::prelude::*; 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; 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::labeled_error( return Err(ShellError::labeled_error(
"Unknown field", "Unknown field",
"object missing field", "object missing field",
span, span,
)))); ));
} }
} }
} }
Some(current.copy()) Ok(current.copy())
} }
pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> { 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 { if let Ok(amount) = amount {
return Ok(args return Ok(args
.input .input
.values
.skip(amount as u64) .skip(amount as u64)
.take(1) .take(1)
.map(|v| ReturnValue::Value(v)) .from_input_stream());
.boxed());
} }
let fields: Result<Vec<(String, Span)>, _> = args let fields: Result<Vec<(String, Span)>, _> = args
@ -51,17 +51,18 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
let stream = args let stream = args
.input .input
.values
.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.0, field.1, &item) { match get_member(&field.0, field.1, &item) {
Some(Value::List(l)) => { Ok(Value::List(l)) => {
for item in 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())), Ok(x) => result.push_back(ReturnSuccess::value(x.copy())),
None => {} Err(_) => {}
} }
} }
@ -69,5 +70,5 @@ pub fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
}) })
.flatten(); .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 span = args.name_span;
let stream = input let stream = input
.values
.map(move |v| match v { .map(move |v| match v {
Value::Primitive(Primitive::String(s)) => { Value::Primitive(Primitive::String(s)) => {
let split_result: Vec<_> = s.lines().filter(|s| s.trim() != "").collect(); 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(); let mut result = VecDeque::new();
for s in split_result { 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(), s.into(),
)))); ))));
} }
@ -26,17 +27,15 @@ pub fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
} }
_ => { _ => {
let mut result = VecDeque::new(); let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new( result.push_back(Err(ShellError::maybe_labeled_error(
ShellError::maybe_labeled_error(
"Expected string values from pipeline", "Expected string values from pipeline",
"expects strings from pipeline", "expects strings from pipeline",
span, span,
), )));
))));
result result
} }
}) })
.flatten(); .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 { for entry in entries {
let value = Value::Object(dir_entry_dict(&entry?)?); 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(); let mut entries = VecDeque::new();
@ -97,7 +97,11 @@ pub fn ls(args: CommandArgs) -> Result<OutputStream, ShellError> {
return Err(ShellError::maybe_labeled_error( return Err(ShellError::maybe_labeled_error(
"Index not closed", "Index not closed",
format!("path missing closing ']'"), 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 { match viewed {
Value::List(l) => { Value::List(l) => {
for item in l { for item in l {
entries.push_back(ReturnValue::Value(item.copy())); entries.push_back(ReturnSuccess::value(item.copy()));
} }
} }
x => { 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::errors::ShellError;
use crate::object::{Primitive, Value}; use crate::object::{Primitive, Switch, Value};
use crate::parser::parse::span::Span; use crate::parser::parse::span::Span;
use crate::parser::registry::{CommandConfig, NamedType}; use crate::parser::registry::NamedType;
use crate::prelude::*; use crate::prelude::*;
use indexmap::IndexMap;
use mime::Mime; use mime::Mime;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
pub struct Open; command! {
Open as open(args, --raw: Switch) {
let span = args.name_span;
impl Command for Open { let cwd = args
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { .env
open(args) .lock()
} .unwrap()
fn name(&self) -> &str { .front()
"open" .unwrap()
} .path()
.to_path_buf();
fn config(&self) -> CommandConfig { let full_path = PathBuf::from(cwd);
let mut named: IndexMap<String, NamedType> = IndexMap::new();
named.insert("raw".to_string(), NamedType::Switch);
CommandConfig { let (file_extension, contents) = match &args.expect_nth(0)?.item {
name: self.name().to_string(), Value::Primitive(Primitive::String(s)) => fetch(&full_path, s, args.expect_nth(0)?.span)?,
mandatory_positional: vec![], _ => {
optional_positional: vec![], return Err(ShellError::labeled_error(
rest_positional: false, "Expected string value for filename",
named, "expected filename",
is_filter: true, args.expect_nth(0)?.span,
is_sink: false, ));
can_load: vec![],
can_save: vec![],
} }
};
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, cwd: &PathBuf,
location: &str, location: &str,
span: Span, span: Span,
) -> Result<(Option<String>, Value), ShellError> { ) -> Result<(Option<String>, String), ShellError> {
let mut cwd = cwd.clone(); let mut cwd = cwd.clone();
if location.starts_with("http:") || location.starts_with("https:") { if location.starts_with("http:") || location.starts_with("https:") {
let response = reqwest::get(location); let response = reqwest::get(location);
@ -168,74 +204,3 @@ pub fn parse_as_value(
_ => Ok(Value::string(contents)), _ => 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 let objects = args
.input .input
.map(move |item| Value::Object(select_fields(&item, &fields))) .values
.map(|item| ReturnValue::Value(item)); .map(move |item| Value::Object(select_fields(&item, &fields)));
let stream = Pin::new(Box::new(objects)); Ok(objects.from_input_stream())
Ok(stream)
} }

View File

@ -93,11 +93,9 @@ pub fn filter_plugin(path: String, args: CommandArgs) -> Result<OutputStream, Sh
}, },
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new( result.push_back(Err(ShellError::string(format!(
ShellError::string(format!(
"Error while processing input: {:?} {}", "Error while processing input: {:?} {}",
e, input e, input
)),
)))); ))));
result result
} }
@ -105,8 +103,9 @@ pub fn filter_plugin(path: String, args: CommandArgs) -> Result<OutputStream, Sh
} }
Err(e) => { Err(e) => {
let mut result = VecDeque::new(); let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new( result.push_back(Err(ShellError::string(format!(
ShellError::string(format!("Error while processing input: {:?}", e)), "Error while processing input: {:?}",
e
)))); ))));
result result
} }
@ -115,7 +114,7 @@ pub fn filter_plugin(path: String, args: CommandArgs) -> Result<OutputStream, Sh
}) })
.flatten(); .flatten();
Ok(stream.boxed()) Ok(stream.to_output_stream())
} }
pub fn sink_plugin(path: String, args: SinkCommandArgs) -> Result<(), ShellError> { 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 let list = list
.into_iter() .into_iter()
.map(|(_, process)| ReturnValue::Value(Value::Object(process_dict(process)))) .map(|(_, process)| Value::Object(process_dict(process)))
.collect::<VecDeque<_>>(); .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 let stream = args
.input .input
.map(move |item| Value::Object(reject_fields(&item, &fields))) .values
.map(|item| ReturnValue::Value(item)); .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(); contents.clear();
} }
Ok(list.boxed()) Ok(list.to_output_stream())
} }
fn count(name: &str, contents: &str) -> ReturnValue { 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("chars", Value::int(chars));
dict.add("max length", Value::int(bytes)); 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 { fn config(&self) -> CommandConfig {
CommandConfig { CommandConfig {
name: self.name().to_string(), name: self.name().to_string(),
mandatory_positional: vec![PositionalType::Block("condition".to_string())], positional: vec![PositionalType::mandatory("condition", "Block")],
optional_positional: vec![],
rest_positional: false, rest_positional: false,
named: indexmap::IndexMap::new(), named: indexmap::IndexMap::new(),
is_filter: true, 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 block = args.nth(0).unwrap().as_block()?;
let input = args.input; 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 result = block.invoke(&item);
let return_value = match result { let return_value = match result {
@ -51,5 +50,5 @@ pub fn skip_while(args: CommandArgs) -> Result<OutputStream, ShellError> {
futures::future::ready(return_value) 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: Result<Vec<_>, _> = args.positional_iter().map(|a| a.as_string()).collect();
let fields = fields?; let fields = fields?;
let output = args.input.collect::<Vec<_>>(); let output = args.input.values.collect::<Vec<_>>();
let output = output.map(move |mut vec| { let output = output.map(move |mut vec| {
vec.sort_by_key(|item| { vec.sort_by_key(|item| {
@ -15,11 +15,8 @@ pub fn sort_by(args: CommandArgs) -> Result<OutputStream, ShellError> {
.collect::<Vec<Option<Value>>>() .collect::<Vec<Option<Value>>>()
}); });
vec.into_iter() vec.into_iter().collect::<VecDeque<_>>()
.map(|v| ReturnValue::Value(v.copy()))
.collect::<VecDeque<_>>()
.boxed()
}); });
Ok(output.flatten_stream().boxed()) Ok(output.flatten_stream().from_input_stream())
} }

View File

@ -20,6 +20,7 @@ pub fn split_column(args: CommandArgs) -> Result<OutputStream, ShellError> {
let input = args.input; let input = args.input;
Ok(input Ok(input
.values
.map(move |v| match v { .map(move |v| match v {
Value::Primitive(Primitive::String(s)) => { Value::Primitive(Primitive::String(s)) => {
let splitter = positional[0].as_string().unwrap().replace("\\n", "\n"); 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()) { for (&k, v) in split_result.iter().zip(gen_columns.iter()) {
dict.add(v.clone(), Value::Primitive(Primitive::String(k.into()))); 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) { } else if split_result.len() == (positional.len() - 1) {
let mut dict = crate::object::Dictionary::default(); let mut dict = crate::object::Dictionary::default();
for (&k, v) in split_result.iter().zip(positional.iter().skip(1)) { 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())), Value::Primitive(Primitive::String(k.into())),
); );
} }
ReturnValue::Value(Value::Object(dict)) ReturnSuccess::value(Value::Object(dict))
} else { } else {
let mut dict = crate::object::Dictionary::default(); let mut dict = crate::object::Dictionary::default();
for k in positional.iter().skip(1) { 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())), 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", "Expected string values from pipeline",
"expects strings from pipeline", "expects strings from pipeline",
span, 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 input = args.input;
let stream = input let stream = input
.values
.map(move |v| match v { .map(move |v| match v {
Value::Primitive(Primitive::String(s)) => { Value::Primitive(Primitive::String(s)) => {
let splitter = positional[0].as_string().unwrap().replace("\\n", "\n"); 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(); let mut result = VecDeque::new();
for s in split_result { 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(), s.into(),
)))); ))));
} }
@ -39,17 +40,15 @@ pub fn split_row(args: CommandArgs) -> Result<OutputStream, ShellError> {
} }
_ => { _ => {
let mut result = VecDeque::new(); let mut result = VecDeque::new();
result.push_back(ReturnValue::Value(Value::Error(Box::new( result.push_back(Err(ShellError::maybe_labeled_error(
ShellError::maybe_labeled_error(
"Expected string values from pipeline", "Expected string values from pipeline",
"expects strings from pipeline", "expects strings from pipeline",
span, span,
), )));
))));
result result
} }
}) })
.flatten(); .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()); // println!("{:#?}", system.get_network());
let mut stream = VecDeque::new(); 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::*; use crate::prelude::*;
pub fn to_array(args: CommandArgs) -> Result<OutputStream, ShellError> { pub fn to_array(args: CommandArgs) -> Result<OutputStream, ShellError> {
let out = args.input.collect(); let out = args.input.values.collect();
Ok(out Ok(out
.map(|vec: Vec<_>| single_output(Value::List(vec))) .map(|vec: Vec<_>| stream![Value::List(vec)])
.flatten_stream() .flatten_stream()
.boxed()) .from_input_stream())
} }

View File

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

View File

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

View File

@ -9,15 +9,16 @@ pub fn trim(args: CommandArgs) -> Result<OutputStream, ShellError> {
let span = args.name_span; let span = args.name_span;
Ok(input Ok(input
.values
.map(move |v| match v { .map(move |v| match v {
Value::Primitive(Primitive::String(s)) => { 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", "Expected string values from pipeline",
"expects strings from pipeline", "expects strings from pipeline",
span, 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()); 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::errors::ShellError;
use crate::parser::registry::{CommandConfig, PositionalType}; use crate::object::Block;
use crate::prelude::*; 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 { input.values.filter_map(move |item| {
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { let result = condition.invoke(&item);
r#where(args)
}
fn name(&self) -> &str {
"where"
}
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![],
}
}
}
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 { let return_value = match result {
Err(err) => Some(ReturnValue::Value(Value::Error(Box::new(err)))), Err(err) => Some(Err(err)),
Ok(v) if v.is_true() => Some(ReturnValue::Value(item.copy())), Ok(v) if v.is_true() => Some(Ok(ReturnSuccess::Value(item.copy()))),
_ => None, _ => None,
}; };
futures::future::ready(return_value) ready(return_value)
}); })
}
Ok(objects.boxed())
} }

View File

@ -41,6 +41,16 @@ pub enum ArgumentError {
MissingValueForName(String), 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)] #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
pub enum ShellError { pub enum ShellError {
String(StringError), String(StringError),
@ -53,6 +63,7 @@ pub enum ShellError {
expr: Description, expr: Description,
}, },
ArgumentError { ArgumentError {
command: String,
error: ArgumentError, error: ArgumentError,
span: Span, span: Span,
}, },
@ -119,28 +130,38 @@ impl ShellError {
ShellError::String(StringError { title, .. }) => { ShellError::String(StringError { title, .. }) => {
Diagnostic::new(Severity::Error, title) Diagnostic::new(Severity::Error, title)
} }
ShellError::ArgumentError { error, span } => match error { ShellError::ArgumentError {
command,
error,
span,
} => match error {
ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new( ArgumentError::MissingMandatoryFlag(name) => Diagnostic::new(
Severity::Error, Severity::Error,
format!( format!(
"Command requires {}{}", "{} requires {}{}",
Color::Cyan.paint("--"), Color::Cyan.paint(command),
Color::Cyan.paint(name) Color::Black.bold().paint("--"),
Color::Black.bold().paint(name)
), ),
) )
.with_label(Label::new_primary(span)), .with_label(Label::new_primary(span)),
ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new( ArgumentError::MissingMandatoryPositional(name) => Diagnostic::new(
Severity::Error, 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)), .with_label(Label::new_primary(span)),
ArgumentError::MissingValueForName(name) => Diagnostic::new( ArgumentError::MissingValueForName(name) => Diagnostic::new(
Severity::Error, Severity::Error,
format!( format!(
"Missing value for flag {}{}", "{} is missing value for flag {}{}",
Color::Cyan.paint("--"), Color::Cyan.paint(command),
Color::Cyan.paint(name) Color::Black.bold().paint("--"),
Color::Black.bold().paint(name)
), ),
) )
.with_label(Label::new_primary(span)), .with_label(Label::new_primary(span)),

View File

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

View File

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

View File

@ -185,9 +185,6 @@ pub enum Value {
#[allow(unused)] #[allow(unused)]
Block(Block), Block(Block),
Filesystem, Filesystem,
#[allow(unused)]
Error(Box<ShellError>),
} }
pub fn debug_list(values: &'a Vec<Value>) -> ValuesDebug<'a> { 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::Object(o) => o.debug(f),
Value::List(l) => debug_list(l).fmt(f), Value::List(l) => debug_list(l).fmt(f),
Value::Block(_) => write!(f, "[[block]]"), Value::Block(_) => write!(f, "[[block]]"),
Value::Error(err) => write!(f, "[[error :: {} ]]", err),
Value::Filesystem => write!(f, "[[filesystem]]"), Value::Filesystem => write!(f, "[[filesystem]]"),
Value::Binary(_) => write!(f, "[[binary]]"), 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 { impl Value {
crate fn type_name(&self) -> String { crate fn type_name(&self) -> String {
match self { match self {
@ -238,7 +293,6 @@ impl Value {
Value::Object(_) => format!("object"), Value::Object(_) => format!("object"),
Value::List(_) => format!("list"), Value::List(_) => format!("list"),
Value::Block(_) => format!("block"), Value::Block(_) => format!("block"),
Value::Error(_) => format!("error"),
Value::Filesystem => format!("filesystem"), Value::Filesystem => format!("filesystem"),
Value::Binary(_) => format!("binary"), Value::Binary(_) => format!("binary"),
} }
@ -312,7 +366,6 @@ impl Value {
let list = l.iter().map(|i| i.copy()).collect(); let list = l.iter().map(|i| i.copy()).collect();
Value::List(list) Value::List(list)
} }
Value::Error(e) => Value::Error(Box::new(e.copy_error())),
Value::Filesystem => Value::Filesystem, Value::Filesystem => Value::Filesystem,
Value::Binary(b) => Value::Binary(b.clone()), Value::Binary(b) => Value::Binary(b.clone()),
} }
@ -329,7 +382,6 @@ impl Value {
), ),
Value::Object(_) => format!("[object Object]"), Value::Object(_) => format!("[object Object]"),
Value::List(_) => format!("[list List]"), Value::List(_) => format!("[list List]"),
Value::Error(e) => format!("{}", e),
Value::Filesystem => format!("<filesystem>"), Value::Filesystem => format!("<filesystem>"),
Value::Binary(_) => format!("<binary>"), Value::Binary(_) => format!("<binary>"),
} }
@ -459,14 +511,6 @@ impl Value {
Ok(Value::Primitive(Primitive::Date(date))) 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 { pub fn nothing() -> Value {
Value::Primitive(Primitive::Nothing) Value::Primitive(Primitive::Nothing)
} }

View File

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

View File

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

View File

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

View File

@ -3,6 +3,9 @@ use nu::{
serve_plugin, Args, CommandConfig, Plugin, PositionalType, Primitive, ReturnValue, ShellError, serve_plugin, Args, CommandConfig, Plugin, PositionalType, Primitive, ReturnValue, ShellError,
Spanned, Value, Spanned, Value,
}; };
use nu::{Primitive, ReturnSuccess, ReturnValue, ShellError, Spanned, Value};
use serde::{Deserialize, Serialize};
use std::io;
struct Inc { struct Inc {
inc_by: i64, inc_by: i64,
@ -13,42 +16,75 @@ impl Inc {
} }
} }
impl Plugin for Inc { fn send_response<T: Serialize>(result: Vec<T>) {
fn config(&mut self) -> Result<CommandConfig, ShellError> { let response = JsonRpc::new("response", result);
Ok(CommandConfig { let response_raw = serde_json::to_string(&response).unwrap();
name: "inc".to_string(), println!("{}", response_raw);
mandatory_positional: vec![], }
optional_positional: vec![PositionalType::Value("Increment".into())],
can_load: vec![], fn send_error(error: ShellError) {
can_save: vec![], let message: ReturnValue = Err(error);
is_filter: true, send_response(vec![message])
is_sink: false, }
named: IndexMap::new(),
rest_positional: true, #[derive(Debug, Serialize, Deserialize)]
}) #[serde(tag = "method")]
} #[allow(non_camel_case_types)]
fn begin_filter(&mut self, args: Args) -> Result<(), ShellError> { pub enum NuCommand {
if let Some(args) = args.positional { init { params: Vec<Spanned<Value>> },
for arg in args { filter { params: Value },
match arg { 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 { Spanned {
item: Value::Primitive(Primitive::Int(i)), item: Value::Primitive(Primitive::Int(i)),
.. ..
} => { } => {
self.inc_by = 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")), _ => return Err(ShellError::string("Unrecognized type in params")),
} }
} }
} Err(_) => {
send_error(ShellError::string("Unrecognized type in stream"));
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))])
} }
Value::Primitive(Primitive::Bytes(b)) => Ok(vec![ReturnValue::Value(Value::bytes( Value::Primitive(Primitive::Bytes(b)) => Ok(vec![ReturnValue::Value(Value::bytes(
b + self.inc_by as u64, 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::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::context::Context;
crate use crate::env::host::handle_unexpected; crate use crate::env::host::handle_unexpected;
crate use crate::env::{Environment, Host}; crate use crate::env::{Environment, Host};
crate use crate::errors::ShellError; crate use crate::errors::ShellError;
crate use crate::object::{Primitive, Value}; 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 crate::Text;
crate use futures::stream::BoxStream;
crate use futures::Stream;
crate use futures::{FutureExt, StreamExt}; crate use futures::{FutureExt, StreamExt};
crate use std::collections::VecDeque; crate use std::collections::VecDeque;
crate use std::pin::Pin; crate use std::future::Future;
crate use std::sync::{Arc, Mutex}; 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 crate::prelude::*;
use futures::stream::BoxStream;
pub type InputStream = BoxStream<'static, Value>; pub struct InputStream {
pub type OutputStream = BoxStream<'static, ReturnValue>; crate values: BoxStream<'static, Value>,
}
crate fn single_output(item: Value) -> OutputStream {
let value = ReturnValue::Value(item); impl InputStream {
let mut vec = VecDeque::new(); pub fn into_vec(self) -> impl Future<Output = Vec<Value>> {
vec.push_back(value); self.values.collect()
vec.boxed() }
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(),
}
}
} }