From 84caf8859fbcae79f9b0f832c5b5c930d2e69f26 Mon Sep 17 00:00:00 2001 From: WindSoilder Date: Sat, 2 Jul 2022 22:54:49 +0800 Subject: [PATCH] add -e flag to print, to print the value to stderr (#5935) * Refactor: make stdout write all and flush as generic function * support print to stderr --- crates/nu-cli/src/print.rs | 4 +++- crates/nu-cli/src/util.rs | 2 +- crates/nu-command/src/filesystem/watch.rs | 2 +- crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-command/tests/commands/print.rs | 23 ++++++++++++++++++++ crates/nu-protocol/src/pipeline_data.rs | 26 +++++++++++++++++++---- crates/nu-utils/src/lib.rs | 4 +--- crates/nu-utils/src/utils.rs | 18 ++++++++++------ 8 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 crates/nu-command/tests/commands/print.rs diff --git a/crates/nu-cli/src/print.rs b/crates/nu-cli/src/print.rs index 499b120cb..5957c488f 100644 --- a/crates/nu-cli/src/print.rs +++ b/crates/nu-cli/src/print.rs @@ -21,6 +21,7 @@ impl Command for Print { "print without inserting a newline for the line ending", Some('n'), ) + .switch("stderr", "print to stderr instead of stdout", Some('e')) .category(Category::Strings) } @@ -48,11 +49,12 @@ Since this command has no output, there is no point in piping it with other comm ) -> Result { let args: Vec = call.rest(engine_state, stack, 0)?; let no_newline = call.has_flag("no-newline"); + let to_stderr = call.has_flag("stderr"); let head = call.head; for arg in args { arg.into_pipeline_data() - .print(engine_state, stack, no_newline)?; + .print(engine_state, stack, no_newline, to_stderr)?; } Ok(PipelineData::new(head)) diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index f36a88b38..48f56254c 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -247,7 +247,7 @@ pub fn eval_source( set_last_exit_code(stack, 0); } - if let Err(err) = pipeline_data.print(engine_state, stack, false) { + if let Err(err) = pipeline_data.print(engine_state, stack, false, false) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &err); diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index a3d368cc4..46b654375 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -216,7 +216,7 @@ impl Command for Watch { match eval_result { Ok(val) => { - val.print(engine_state, stack, false)?; + val.print(engine_state, stack, false, false)?; } Err(err) => { let working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 0c56bacb2..fd127dc32 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -44,6 +44,7 @@ mod open; mod parse; mod path; mod prepend; +mod print; #[cfg(feature = "database")] mod query; mod random; diff --git a/crates/nu-command/tests/commands/print.rs b/crates/nu-command/tests/commands/print.rs new file mode 100644 index 000000000..4882af88e --- /dev/null +++ b/crates/nu-command/tests/commands/print.rs @@ -0,0 +1,23 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn print_to_stdout() { + let actual = nu!( + cwd: ".", pipeline( + "print 'hello world'" + ) + ); + assert!(actual.out.contains("hello world")); + assert!(actual.err.is_empty()); +} + +#[test] +fn print_to_stderr() { + let actual = nu!( + cwd: ".", pipeline( + "print -e 'hello world'" + ) + ); + assert!(actual.out.is_empty()); + assert!(actual.err.contains("hello world")); +} diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 3dba1628a..7f818335a 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -3,7 +3,7 @@ use crate::{ engine::{EngineState, Stack, StateWorkingSet}, format_error, Config, ListStream, RawStream, ShellError, Span, Value, }; -use nu_utils::{stdout_write_all_and_flush, stdout_write_all_as_binary_and_flush}; +use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush}; use std::sync::{atomic::AtomicBool, Arc}; /// The foundational abstraction for input and output to commands @@ -414,11 +414,16 @@ impl PipelineData { } } + /// Consume and print self data immediately. + /// + /// `no_newline` controls if we need to attach newline character to output. + /// `to_stderr` controls if data is output to stderr, when the value is false, the data is ouput to stdout. pub fn print( self, engine_state: &EngineState, stack: &mut Stack, no_newline: bool, + to_stderr: bool, ) -> Result<(), ShellError> { // If the table function is in the declarations, then we can use it // to create the table value that will be printed in the terminal @@ -436,7 +441,12 @@ impl PipelineData { for s in stream { let s_live = s?; let bin_output = s_live.as_binary()?; - stdout_write_all_as_binary_and_flush(bin_output)? + + if !to_stderr { + stdout_write_all_and_flush(bin_output)? + } else { + stderr_write_all_and_flush(bin_output)? + } } } @@ -472,7 +482,11 @@ impl PipelineData { out.push('\n'); } - stdout_write_all_and_flush(out)? + if !to_stderr { + stdout_write_all_and_flush(out)? + } else { + stderr_write_all_and_flush(out)? + } } } None => { @@ -491,7 +505,11 @@ impl PipelineData { out.push('\n'); } - stdout_write_all_and_flush(out)? + if !to_stderr { + stdout_write_all_and_flush(out)? + } else { + stderr_write_all_and_flush(out)? + } } } }; diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index 07b0fbea1..7e2c9c90a 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -1,5 +1,3 @@ pub mod utils; -pub use utils::{ - enable_vt_processing, stdout_write_all_and_flush, stdout_write_all_as_binary_and_flush, -}; +pub use utils::{enable_vt_processing, stderr_write_all_and_flush, stdout_write_all_and_flush}; diff --git a/crates/nu-utils/src/utils.rs b/crates/nu-utils/src/utils.rs index bab567be1..9d4ee9897 100644 --- a/crates/nu-utils/src/utils.rs +++ b/crates/nu-utils/src/utils.rs @@ -24,9 +24,12 @@ pub fn enable_vt_processing() -> Result<()> { Ok(()) } -pub fn stdout_write_all_and_flush(output: String) -> Result<()> { +pub fn stdout_write_all_and_flush(output: T) -> Result<()> +where + T: AsRef<[u8]>, +{ let stdout = std::io::stdout(); - let ret = match stdout.lock().write_all(output.as_bytes()) { + let ret = match stdout.lock().write_all(output.as_ref()) { Ok(_) => Ok(stdout.lock().flush()?), Err(err) => Err(err), }; @@ -34,10 +37,13 @@ pub fn stdout_write_all_and_flush(output: String) -> Result<()> { ret } -pub fn stdout_write_all_as_binary_and_flush(output: &[u8]) -> Result<()> { - let stdout = std::io::stdout(); - let ret = match stdout.lock().write_all(output) { - Ok(_) => Ok(stdout.lock().flush()?), +pub fn stderr_write_all_and_flush(output: T) -> Result<()> +where + T: AsRef<[u8]>, +{ + let stderr = std::io::stderr(); + let ret = match stderr.lock().write_all(output.as_ref()) { + Ok(_) => Ok(stderr.lock().flush()?), Err(err) => Err(err), };