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, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
format_duration, format_filesize_from_conf, Category, Config, Example, IntoPipelineData, format_duration, format_filesize_from_conf, Category, Config, Example, IntoPipelineData,
PipelineData, ShellError, Signature, Type, Value, ListStream, PipelineData, RawStream, ShellError, Signature, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -40,13 +40,34 @@ impl Command for ToText {
"\n" "\n"
}; };
let collected_input = local_into_string(input.into_value(span), line_ending, config); 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 { Ok(Value::String {
val: collected_input, val: collected_input,
span, span,
}
.into_pipeline_data())
} }
.into_pipeline_data())
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -58,18 +79,38 @@ impl Command for ToText {
}, },
Example { Example {
description: "Outputs external data as simple text", description: "Outputs external data as simple text",
example: "git help -a | lines | find -r '^ ' | to text", example: "git help -a | lines | find -r '^ ' | to text",
result: None, result: None,
}, },
Example { Example {
description: "Outputs records as simple text", description: "Outputs records as simple text",
example: "ls | to text", example: "ls | to text",
result: None, result: None,
}, },
] ]
} }
} }
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 { fn local_into_string(value: Value, separator: &str, config: &Config) -> String {
match value { match value {
Value::Bool { val, .. } => val.to_string(), Value::Bool { val, .. } => val.to_string(),

View File

@ -90,6 +90,7 @@ mod split_row;
mod str_; mod str_;
mod table; mod table;
mod take; mod take;
mod to_text;
mod touch; mod touch;
mod transpose; mod transpose;
mod try_; 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");
}