diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 8083c45dba..c0e059eea5 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -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,13 +40,34 @@ impl Command for ToText { "\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 { - val: collected_input, - span, + Ok(Value::String { + val: collected_input, + span, + } + .into_pipeline_data()) } - .into_pipeline_data()) } fn examples(&self) -> Vec { @@ -58,18 +79,38 @@ impl Command for ToText { }, Example { 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, }, Example { description: "Outputs records as simple text", - example: "ls | to text", + example: "ls | to text", result: None, }, ] } } +struct ListStreamIterator { + stream: ListStream, + separator: String, + config: Config, +} + +impl Iterator for ListStreamIterator { + type Item = Result, ShellError>; + + fn next(&mut self) -> Option { + 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(), diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 8d0e53f929..232af3ff20 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -90,6 +90,7 @@ mod split_row; mod str_; mod table; mod take; +mod to_text; mod touch; mod transpose; mod try_; diff --git a/crates/nu-command/tests/commands/to_text.rs b/crates/nu-command/tests/commands/to_text.rs new file mode 100644 index 0000000000..f51e3838fd --- /dev/null +++ b/crates/nu-command/tests/commands/to_text.rs @@ -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"); +}