Make to text stream ListStreams (#7577)

This PR changes `to text` so that when given a `ListStream`, it streams
the incoming values instead of collecting them all first.

The easiest way to observe/verify this PR is to convert a list to a very
slow `ListStream` with `each`:
```bash
ls | get name | each {|n| sleep 1sec; $n} | to text
```
The `to text` output will appear 1 item at a time.
This commit is contained in:
Reilly Wood 2022-12-22 16:38:07 -08:00 committed by GitHub
parent 6fc5244439
commit 9364bad625
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 8 deletions

View File

@ -3,7 +3,7 @@ use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
format_duration, format_filesize_from_conf, Category, Config, Example, IntoPipelineData,
PipelineData, ShellError, Signature, Type, Value,
ListStream, PipelineData, RawStream, ShellError, Signature, Type, Value,
};
#[derive(Clone)]
@ -40,6 +40,26 @@ impl Command for ToText {
"\n"
};
if let PipelineData::ListStream(stream, _) = input {
Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(ListStreamIterator {
stream,
separator: line_ending.into(),
config: config.clone(),
}),
engine_state.ctrlc.clone(),
span,
)),
stderr: None,
exit_code: None,
span,
metadata: None,
trim_end_newline: false,
})
} else {
// FIXME: don't collect! stream the output wherever possible!
// Even if the data is collected when it arrives at `to text`, we should be able to stream it out
let collected_input = local_into_string(input.into_value(span), line_ending, config);
Ok(Value::String {
@ -48,6 +68,7 @@ impl Command for ToText {
}
.into_pipeline_data())
}
}
fn examples(&self) -> Vec<Example> {
vec![
@ -70,6 +91,26 @@ impl Command for ToText {
}
}
struct ListStreamIterator {
stream: ListStream,
separator: String,
config: Config,
}
impl Iterator for ListStreamIterator {
type Item = Result<Vec<u8>, ShellError>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.stream.next() {
let mut string = local_into_string(item, &self.separator, &self.config);
string.push_str(&self.separator);
Some(Ok(string.as_bytes().to_vec()))
} else {
None
}
}
}
fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
match value {
Value::Bool { val, .. } => val.to_string(),

View File

@ -90,6 +90,7 @@ mod split_row;
mod str_;
mod table;
mod take;
mod to_text;
mod touch;
mod transpose;
mod try_;

View File

@ -0,0 +1,19 @@
use nu_test_support::nu;
#[test]
fn list_to_text() {
let actual = nu!(r#"["foo" "bar" "baz"] | to text"#);
// these actually have newlines between them in the real world but nu! strips newlines, grr
assert_eq!(actual.out, "foobarbaz");
}
// the output should be the same when `to text` gets a ListStream instead of a Value::List
#[test]
fn list_stream_to_text() {
// use `each` to convert the list to a ListStream
let actual = nu!(r#"["foo" "bar" "baz"] | each {|i| $i} | to text"#);
// these actually have newlines between them in the real world but nu! strips newlines, grr
assert_eq!(actual.out, "foobarbaz");
}