Add --raw switch to print for binary data (#13597)

# Description

Something I meant to add a long time ago. We currently don't have a
convenient way to print raw binary data intentionally. You can pipe it
through `cat` to turn it into an unknown stream, or write it to a file
and read it again, but we can't really just e.g. generate msgpack and
write it to stdout without this. For example:

```nushell
[abc def] | to msgpack | print --raw
```

This is useful for nushell scripts that will be piped into something
else. It also means that `nu_plugin_nu_example` probably doesn't need to
do this anymore, but I haven't adjusted it yet:

```nushell
def tell_nushell_encoding [] {
  print -n "\u{0004}json"
}
```

This happens to work because 0x04 is a valid UTF-8 character, but it
wouldn't be possible if it were something above 0x80.

`--raw` also formats other things without `table`, I figured the two
things kind of go together. The output is kind of like `to text`.
Debatable whether that should share the same flag, but it was easier
that way and seemed reasonable.

# User-Facing Changes
- `print` new flag: `--raw`

# Tests + Formatting
Added tests.

# After Submitting
- [ ] release notes (command modified)
This commit is contained in:
Devyn Cairns 2024-08-12 02:29:25 -07:00 committed by GitHub
parent 18772b73b3
commit 4e205cd9a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 3 deletions

View file

@ -22,6 +22,11 @@ impl Command for Print {
Some('n'), Some('n'),
) )
.switch("stderr", "print to stderr instead of stdout", Some('e')) .switch("stderr", "print to stderr instead of stdout", Some('e'))
.switch(
"raw",
"print without formatting (including binary data)",
Some('r'),
)
.category(Category::Strings) .category(Category::Strings)
} }
@ -50,16 +55,26 @@ Since this command has no output, there is no point in piping it with other comm
let args: Vec<Value> = call.rest(engine_state, stack, 0)?; let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
let no_newline = call.has_flag(engine_state, stack, "no-newline")?; let no_newline = call.has_flag(engine_state, stack, "no-newline")?;
let to_stderr = call.has_flag(engine_state, stack, "stderr")?; let to_stderr = call.has_flag(engine_state, stack, "stderr")?;
let raw = call.has_flag(engine_state, stack, "raw")?;
// This will allow for easy printing of pipelines as well // This will allow for easy printing of pipelines as well
if !args.is_empty() { if !args.is_empty() {
for arg in args { for arg in args {
if raw {
arg.into_pipeline_data()
.print_raw(engine_state, no_newline, to_stderr)?;
} else {
arg.into_pipeline_data() arg.into_pipeline_data()
.print(engine_state, stack, no_newline, to_stderr)?; .print(engine_state, stack, no_newline, to_stderr)?;
} }
}
} else if !input.is_nothing() { } else if !input.is_nothing() {
if raw {
input.print_raw(engine_state, no_newline, to_stderr)?;
} else {
input.print(engine_state, stack, no_newline, to_stderr)?; input.print(engine_state, stack, no_newline, to_stderr)?;
} }
}
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }
@ -76,6 +91,11 @@ Since this command has no output, there is no point in piping it with other comm
example: r#"print (2 + 3)"#, example: r#"print (2 + 3)"#,
result: None, result: None,
}, },
Example {
description: "Print 'ABC' from binary data",
example: r#"0x[41 42 43] | print --raw"#,
result: None,
},
] ]
} }
} }

View file

@ -13,3 +13,15 @@ fn print_to_stderr() {
assert!(actual.out.is_empty()); assert!(actual.out.is_empty());
assert!(actual.err.contains("hello world")); assert!(actual.err.contains("hello world"));
} }
#[test]
fn print_raw() {
let actual = nu!("0x[41 42 43] | print --raw");
assert_eq!(actual.out, "ABC");
}
#[test]
fn print_raw_stream() {
let actual = nu!("[0x[66] 0x[6f 6f]] | bytes collect | print --raw");
assert_eq!(actual.out, "foo");
}

View file

@ -604,6 +604,31 @@ impl PipelineData {
} }
} }
/// Consume and print self data without any extra formatting.
///
/// This does not use the `table` command to format data, and also prints binary values and
/// streams in their raw format without generating a hexdump first.
///
/// `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 output to stdout.
pub fn print_raw(
self,
engine_state: &EngineState,
no_newline: bool,
to_stderr: bool,
) -> Result<Option<ExitStatus>, ShellError> {
if let PipelineData::Value(Value::Binary { val: bytes, .. }, _) = self {
if to_stderr {
stderr_write_all_and_flush(bytes)?;
} else {
stdout_write_all_and_flush(bytes)?;
}
Ok(None)
} else {
self.write_all_and_flush(engine_state, no_newline, to_stderr)
}
}
fn write_all_and_flush( fn write_all_and_flush(
self, self,
engine_state: &EngineState, engine_state: &EngineState,