forked from extern/nushell
Make every
stream-able (#2120)
* Make every stream-able * Make each over ranges stream-able
This commit is contained in:
parent
a1a0710ee6
commit
c3ba1e476f
@ -3,7 +3,7 @@ use crate::prelude::*;
|
|||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::hir::Operator;
|
use nu_protocol::hir::Operator;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Primitive, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
Primitive, Range, RangeInclusion, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Echo;
|
pub struct Echo;
|
||||||
@ -55,60 +55,89 @@ async fn echo(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStr
|
|||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
let (args, _): (EchoArgs, _) = args.process(®istry).await?;
|
||||||
|
|
||||||
let stream = args.rest.into_iter().map(|i| {
|
let stream = args.rest.into_iter().map(|i| match i.as_string() {
|
||||||
match i.as_string() {
|
Ok(s) => OutputStream::one(Ok(ReturnSuccess::Value(
|
||||||
Ok(s) => {
|
UntaggedValue::string(s).into_value(i.tag.clone()),
|
||||||
OutputStream::one(Ok(ReturnSuccess::Value(
|
))),
|
||||||
UntaggedValue::string(s).into_value(i.tag.clone()),
|
_ => match i {
|
||||||
)))
|
Value {
|
||||||
}
|
value: UntaggedValue::Table(table),
|
||||||
_ => match i {
|
..
|
||||||
Value {
|
} => futures::stream::iter(table.into_iter().map(ReturnSuccess::value))
|
||||||
value: UntaggedValue::Table(table),
|
.to_output_stream(),
|
||||||
..
|
Value {
|
||||||
} => {
|
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
||||||
futures::stream::iter(table.into_iter().map(ReturnSuccess::value)).to_output_stream()
|
tag,
|
||||||
}
|
} => futures::stream::iter(RangeIterator::new(*range, tag)).to_output_stream(),
|
||||||
Value {
|
_ => OutputStream::one(Ok(ReturnSuccess::Value(i.clone()))),
|
||||||
value: UntaggedValue::Primitive(Primitive::Range(range)),
|
},
|
||||||
tag
|
|
||||||
} => {
|
|
||||||
let mut output_vec = vec![];
|
|
||||||
|
|
||||||
let mut current = range.from.0.item;
|
|
||||||
while current != range.to.0.item {
|
|
||||||
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current.clone()).into_value(&tag))));
|
|
||||||
current = match crate::data::value::compute_values(Operator::Plus, &UntaggedValue::Primitive(current), &UntaggedValue::int(1)) {
|
|
||||||
Ok(result) => match result {
|
|
||||||
UntaggedValue::Primitive(p) => p,
|
|
||||||
_ => {
|
|
||||||
return OutputStream::one(Err(ShellError::unimplemented("Internal error: expected a primitive result from increment")));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err((left_type, right_type)) => {
|
|
||||||
return OutputStream::one(Err(ShellError::coerce_error(
|
|
||||||
left_type.spanned(tag.span),
|
|
||||||
right_type.spanned(tag.span),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let RangeInclusion::Inclusive = range.to.1 {
|
|
||||||
output_vec.push(Ok(ReturnSuccess::Value(UntaggedValue::Primitive(current).into_value(&tag))));
|
|
||||||
}
|
|
||||||
|
|
||||||
futures::stream::iter(output_vec.into_iter()).to_output_stream()
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
OutputStream::one(Ok(ReturnSuccess::Value(i.clone())))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
Ok(futures::stream::iter(stream).flatten().to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct RangeIterator {
|
||||||
|
curr: Primitive,
|
||||||
|
end: Primitive,
|
||||||
|
tag: Tag,
|
||||||
|
is_end_inclusive: bool,
|
||||||
|
is_done: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RangeIterator {
|
||||||
|
pub fn new(range: Range, tag: Tag) -> RangeIterator {
|
||||||
|
RangeIterator {
|
||||||
|
curr: range.from.0.item,
|
||||||
|
end: range.to.0.item,
|
||||||
|
tag,
|
||||||
|
is_end_inclusive: match range.to.1 {
|
||||||
|
RangeInclusion::Inclusive => true,
|
||||||
|
RangeInclusion::Exclusive => false,
|
||||||
|
},
|
||||||
|
is_done: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for RangeIterator {
|
||||||
|
type Item = Result<ReturnSuccess, ShellError>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.curr != self.end {
|
||||||
|
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||||
|
|
||||||
|
self.curr = match crate::data::value::compute_values(
|
||||||
|
Operator::Plus,
|
||||||
|
&UntaggedValue::Primitive(self.curr.clone()),
|
||||||
|
&UntaggedValue::int(1),
|
||||||
|
) {
|
||||||
|
Ok(result) => match result {
|
||||||
|
UntaggedValue::Primitive(p) => p,
|
||||||
|
_ => {
|
||||||
|
return Some(Err(ShellError::unimplemented(
|
||||||
|
"Internal error: expected a primitive result from increment",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err((left_type, right_type)) => {
|
||||||
|
return Some(Err(ShellError::coerce_error(
|
||||||
|
left_type.spanned(self.tag.span),
|
||||||
|
right_type.spanned(self.tag.span),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(ReturnSuccess::value(output))
|
||||||
|
} else if self.is_end_inclusive && !self.is_done {
|
||||||
|
self.is_done = true;
|
||||||
|
Some(ReturnSuccess::value(
|
||||||
|
UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone()),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
// TODO: add inclusive/exclusive ranges
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Echo;
|
use super::Echo;
|
||||||
|
@ -71,20 +71,23 @@ impl WholeStreamCommand for Every {
|
|||||||
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
async fn every(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||||
let registry = registry.clone();
|
let registry = registry.clone();
|
||||||
let (EveryArgs { stride, skip }, input) = args.process(®istry).await?;
|
let (EveryArgs { stride, skip }, input) = args.process(®istry).await?;
|
||||||
let v: Vec<_> = input.into_vec().await;
|
|
||||||
|
|
||||||
let iter = v.into_iter().enumerate().filter_map(move |(i, value)| {
|
let stride = stride.item;
|
||||||
let stride_desired = if stride.item < 1 { 1 } else { stride.item } as usize;
|
let skip = skip.item;
|
||||||
let should_include = skip.item == (i % stride_desired != 0);
|
|
||||||
|
|
||||||
if should_include {
|
Ok(input
|
||||||
return Some(ReturnSuccess::value(value));
|
.enumerate()
|
||||||
}
|
.filter_map(move |(i, value)| async move {
|
||||||
|
let stride_desired = if stride < 1 { 1 } else { stride } as usize;
|
||||||
|
let should_include = skip == (i % stride_desired != 0);
|
||||||
|
|
||||||
None
|
if should_include {
|
||||||
});
|
Some(ReturnSuccess::value(value))
|
||||||
|
} else {
|
||||||
Ok(futures::stream::iter(iter).to_output_stream())
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -55,7 +55,7 @@ impl Clone for FilesystemShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FilesystemShell {
|
impl FilesystemShell {
|
||||||
pub fn basic(commands: CommandRegistry) -> Result<FilesystemShell, std::io::Error> {
|
pub fn basic(commands: CommandRegistry) -> Result<FilesystemShell, Error> {
|
||||||
let path = std::env::current_dir()?;
|
let path = std::env::current_dir()?;
|
||||||
|
|
||||||
Ok(FilesystemShell {
|
Ok(FilesystemShell {
|
||||||
@ -162,7 +162,7 @@ impl Shell for FilesystemShell {
|
|||||||
let metadata = match std::fs::symlink_metadata(&path) {
|
let metadata = match std::fs::symlink_metadata(&path) {
|
||||||
Ok(metadata) => Some(metadata),
|
Ok(metadata) => Some(metadata),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
if e.kind() == ErrorKind::PermissionDenied {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
return Some(Err(e.into()));
|
return Some(Err(e.into()));
|
||||||
|
13
crates/nu-cli/tests/commands/echo.rs
Normal file
13
crates/nu-cli/tests/commands/echo.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn echo_range_is_lazy() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: "tests/fixtures/formats", pipeline(
|
||||||
|
r#"
|
||||||
|
echo 1..10000000000 | first 3 | echo $it | to json
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual.out, "[1,2,3]");
|
||||||
|
}
|
@ -11,6 +11,7 @@ mod cp;
|
|||||||
mod default;
|
mod default;
|
||||||
mod drop;
|
mod drop;
|
||||||
mod each;
|
mod each;
|
||||||
|
mod echo;
|
||||||
mod enter;
|
mod enter;
|
||||||
mod every;
|
mod every;
|
||||||
mod first;
|
mod first;
|
||||||
|
Loading…
Reference in New Issue
Block a user