From b7b930c3f6d6a22bb6513dc1c2f40da5a344f5aa Mon Sep 17 00:00:00 2001 From: sholderbach Date: Thu, 19 Dec 2024 15:25:56 +0100 Subject: [PATCH] Rename/deprecate `into bits` to `format bits` `into bits` is a bad name because it is not a traditional type cast to a `bits` type like all the other `into` commands. Instead it is a pretty printer generating `string` type output. Thus the correct bucket is `format` and its subcommands. --- crates/nu-cmd-extra/src/extra/bits/into.rs | 153 ++--------- crates/nu-cmd-extra/src/extra/mod.rs | 1 + .../src/extra/strings/format/bits.rs | 245 ++++++++++++++++++ .../src/extra/strings/format/mod.rs | 3 + .../commands/bits/{into.rs => format.rs} | 4 +- tests/repl/test_bits.rs | 36 +-- 6 files changed, 284 insertions(+), 158 deletions(-) create mode 100644 crates/nu-cmd-extra/src/extra/strings/format/bits.rs rename crates/nu-cmd-extra/tests/commands/bits/{into.rs => format.rs} (58%) diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index 3452c1eeee..fb5cc43ea8 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -1,20 +1,6 @@ -use std::io::{self, Read, Write}; - -use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::Signals; -use num_traits::ToPrimitive; - -pub struct Arguments { - cell_paths: Option>, -} - -impl CmdArgument for Arguments { - fn take_cell_paths(&mut self) -> Option> { - self.cell_paths.take() - } -} +use nu_protocol::{report_parse_warning, ParseWarning}; #[derive(Clone)] pub struct BitsInto; @@ -42,15 +28,15 @@ impl Command for BitsInto { SyntaxShape::CellPath, "for a data structure input, convert data at the given cell paths", ) - .category(Category::Conversions) + .category(Category::Deprecated) } fn description(&self) -> &str { - "Convert value to a binary primitive." + "Convert value to a binary string." } fn search_terms(&self) -> Vec<&str> { - vec!["convert", "cast"] + vec![] } fn run( @@ -60,7 +46,17 @@ impl Command for BitsInto { call: &Call, input: PipelineData, ) -> Result { - into_bits(engine_state, stack, call, input) + let head = call.head; + report_parse_warning( + &StateWorkingSet::new(engine_state), + &ParseWarning::DeprecatedWarning { + old_command: "into bits".into(), + new_suggestion: "use `format bits`".into(), + span: head, + url: "`help format bits`".into(), + }, + ); + crate::extra::strings::format::format_bits(engine_state, stack, call, input) } fn examples(&self) -> Vec { @@ -111,125 +107,6 @@ impl Command for BitsInto { } } -fn into_bits( - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, -) -> Result { - let head = call.head; - let cell_paths = call.rest(engine_state, stack, 0)?; - let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - - if let PipelineData::ByteStream(stream, metadata) = input { - Ok(PipelineData::ByteStream( - byte_stream_to_bits(stream, head), - metadata, - )) - } else { - let args = Arguments { cell_paths }; - operate(action, args, input, call.head, engine_state.signals()) - } -} - -fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { - if let Some(mut reader) = stream.reader() { - let mut is_first = true; - ByteStream::from_fn( - head, - Signals::empty(), - ByteStreamType::String, - move |buffer| { - let mut byte = [0]; - if reader.read(&mut byte[..]).err_span(head)? > 0 { - // Format the byte as bits - if is_first { - is_first = false; - } else { - buffer.push(b' '); - } - write!(buffer, "{:08b}", byte[0]).expect("format failed"); - Ok(true) - } else { - // EOF - Ok(false) - } - }, - ) - } else { - ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String) - } -} - -fn convert_to_smallest_number_type(num: i64, span: Span) -> Value { - if let Some(v) = num.to_i8() { - let bytes = v.to_ne_bytes(); - let mut raw_string = "".to_string(); - for ch in bytes { - raw_string.push_str(&format!("{:08b} ", ch)); - } - Value::string(raw_string.trim(), span) - } else if let Some(v) = num.to_i16() { - let bytes = v.to_ne_bytes(); - let mut raw_string = "".to_string(); - for ch in bytes { - raw_string.push_str(&format!("{:08b} ", ch)); - } - Value::string(raw_string.trim(), span) - } else if let Some(v) = num.to_i32() { - let bytes = v.to_ne_bytes(); - let mut raw_string = "".to_string(); - for ch in bytes { - raw_string.push_str(&format!("{:08b} ", ch)); - } - Value::string(raw_string.trim(), span) - } else { - let bytes = num.to_ne_bytes(); - let mut raw_string = "".to_string(); - for ch in bytes { - raw_string.push_str(&format!("{:08b} ", ch)); - } - Value::string(raw_string.trim(), span) - } -} - -pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value { - match input { - Value::Binary { val, .. } => { - let mut raw_string = "".to_string(); - for ch in val { - raw_string.push_str(&format!("{:08b} ", ch)); - } - Value::string(raw_string.trim(), span) - } - Value::Int { val, .. } => convert_to_smallest_number_type(*val, span), - Value::Filesize { val, .. } => convert_to_smallest_number_type(val.get(), span), - Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span), - Value::String { val, .. } => { - let raw_bytes = val.as_bytes(); - let mut raw_string = "".to_string(); - for ch in raw_bytes { - raw_string.push_str(&format!("{:08b} ", ch)); - } - Value::string(raw_string.trim(), span) - } - Value::Bool { val, .. } => { - let v = >::from(*val); - convert_to_smallest_number_type(v, span) - } - // Propagate errors by explicitly matching them before the final case. - Value::Error { .. } => input.clone(), - other => Value::error( - ShellError::OnlySupportsThisInputType { - exp_input_type: "int, filesize, string, duration, binary, or bool".into(), - wrong_type: other.get_type().to_string(), - dst_span: span, - src_span: other.span(), - }, - span, - ), - } -} #[cfg(test)] mod test { diff --git a/crates/nu-cmd-extra/src/extra/mod.rs b/crates/nu-cmd-extra/src/extra/mod.rs index 8204f1bd9a..0874824ac5 100644 --- a/crates/nu-cmd-extra/src/extra/mod.rs +++ b/crates/nu-cmd-extra/src/extra/mod.rs @@ -46,6 +46,7 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState { bind_command!( strings::format::FormatPattern, + strings::format::FormatBits, strings::str_::case::Str, strings::str_::case::StrCamelCase, strings::str_::case::StrKebabCase, diff --git a/crates/nu-cmd-extra/src/extra/strings/format/bits.rs b/crates/nu-cmd-extra/src/extra/strings/format/bits.rs new file mode 100644 index 0000000000..693c6905e8 --- /dev/null +++ b/crates/nu-cmd-extra/src/extra/strings/format/bits.rs @@ -0,0 +1,245 @@ +use std::io::{self, Read, Write}; + +use nu_cmd_base::input_handler::{operate, CmdArgument}; +use nu_engine::command_prelude::*; + +use nu_protocol::Signals; +use num_traits::ToPrimitive; + +struct Arguments { + cell_paths: Option>, +} + +impl CmdArgument for Arguments { + fn take_cell_paths(&mut self) -> Option> { + self.cell_paths.take() + } +} + +#[derive(Clone)] +pub struct FormatBits; + +impl Command for FormatBits { + fn name(&self) -> &str { + "format bits" + } + + fn signature(&self) -> Signature { + Signature::build("format bits") + .input_output_types(vec![ + (Type::Binary, Type::String), + (Type::Int, Type::String), + (Type::Filesize, Type::String), + (Type::Duration, Type::String), + (Type::String, Type::String), + (Type::Bool, Type::String), + (Type::table(), Type::table()), + (Type::record(), Type::record()), + ]) + .allow_variants_without_examples(true) // TODO: supply exhaustive examples + .rest( + "rest", + SyntaxShape::CellPath, + "for a data structure input, convert data at the given cell paths", + ) + .category(Category::Conversions) + } + + fn description(&self) -> &str { + "Convert value to a string of binary data represented by 0 and 1." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["convert", "cast", "binary"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + format_bits(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "convert a binary value into a string, padded to 8 places with 0s", + example: "0x[1] | format bits", + result: Some(Value::string("00000001", + Span::test_data(), + )), + }, + Example { + description: "convert an int into a string, padded to 8 places with 0s", + example: "1 | format bits", + result: Some(Value::string("00000001", + Span::test_data(), + )), + }, + Example { + description: "convert a filesize value into a string, padded to 8 places with 0s", + example: "1b | format bits", + result: Some(Value::string("00000001", + Span::test_data(), + )), + }, + Example { + description: "convert a duration value into a string, padded to 8 places with 0s", + example: "1ns | format bits", + result: Some(Value::string("00000001", + Span::test_data(), + )), + }, + Example { + description: "convert a boolean value into a string, padded to 8 places with 0s", + example: "true | format bits", + result: Some(Value::string("00000001", + Span::test_data(), + )), + }, + Example { + description: "convert a string into a raw binary string, padded with 0s to 8 places", + example: "'nushell.sh' | format bits", + result: Some(Value::string("01101110 01110101 01110011 01101000 01100101 01101100 01101100 00101110 01110011 01101000", + Span::test_data(), + )), + }, + ] + } +} + +// TODO: crate public only during deprecation +pub(crate) fn format_bits( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let head = call.head; + let cell_paths = call.rest(engine_state, stack, 0)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + + if let PipelineData::ByteStream(stream, metadata) = input { + Ok(PipelineData::ByteStream( + byte_stream_to_bits(stream, head), + metadata, + )) + } else { + let args = Arguments { cell_paths }; + operate(action, args, input, call.head, engine_state.signals()) + } +} + +fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { + if let Some(mut reader) = stream.reader() { + let mut is_first = true; + ByteStream::from_fn( + head, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + let mut byte = [0]; + if reader.read(&mut byte[..]).err_span(head)? > 0 { + // Format the byte as bits + if is_first { + is_first = false; + } else { + buffer.push(b' '); + } + write!(buffer, "{:08b}", byte[0]).expect("format failed"); + Ok(true) + } else { + // EOF + Ok(false) + } + }, + ) + } else { + ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String) + } +} + +fn convert_to_smallest_number_type(num: i64, span: Span) -> Value { + if let Some(v) = num.to_i8() { + let bytes = v.to_ne_bytes(); + let mut raw_string = "".to_string(); + for ch in bytes { + raw_string.push_str(&format!("{:08b} ", ch)); + } + Value::string(raw_string.trim(), span) + } else if let Some(v) = num.to_i16() { + let bytes = v.to_ne_bytes(); + let mut raw_string = "".to_string(); + for ch in bytes { + raw_string.push_str(&format!("{:08b} ", ch)); + } + Value::string(raw_string.trim(), span) + } else if let Some(v) = num.to_i32() { + let bytes = v.to_ne_bytes(); + let mut raw_string = "".to_string(); + for ch in bytes { + raw_string.push_str(&format!("{:08b} ", ch)); + } + Value::string(raw_string.trim(), span) + } else { + let bytes = num.to_ne_bytes(); + let mut raw_string = "".to_string(); + for ch in bytes { + raw_string.push_str(&format!("{:08b} ", ch)); + } + Value::string(raw_string.trim(), span) + } +} + +fn action(input: &Value, _args: &Arguments, span: Span) -> Value { + match input { + Value::Binary { val, .. } => { + let mut raw_string = "".to_string(); + for ch in val { + raw_string.push_str(&format!("{:08b} ", ch)); + } + Value::string(raw_string.trim(), span) + } + Value::Int { val, .. } => convert_to_smallest_number_type(*val, span), + Value::Filesize { val, .. } => convert_to_smallest_number_type(val.get(), span), + Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span), + Value::String { val, .. } => { + let raw_bytes = val.as_bytes(); + let mut raw_string = "".to_string(); + for ch in raw_bytes { + raw_string.push_str(&format!("{:08b} ", ch)); + } + Value::string(raw_string.trim(), span) + } + Value::Bool { val, .. } => { + let v = >::from(*val); + convert_to_smallest_number_type(v, span) + } + // Propagate errors by explicitly matching them before the final case. + Value::Error { .. } => input.clone(), + other => Value::error( + ShellError::OnlySupportsThisInputType { + exp_input_type: "int, filesize, string, duration, binary, or bool".into(), + wrong_type: other.get_type().to_string(), + dst_span: span, + src_span: other.span(), + }, + span, + ), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(FormatBits {}) + } +} diff --git a/crates/nu-cmd-extra/src/extra/strings/format/mod.rs b/crates/nu-cmd-extra/src/extra/strings/format/mod.rs index 8692a912ff..1a703002bb 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/mod.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/mod.rs @@ -1,3 +1,6 @@ mod command; +mod bits; pub(crate) use command::FormatPattern; +// TODO remove `format_bits` visibility after removal of into bits +pub(crate) use bits::{format_bits, FormatBits}; diff --git a/crates/nu-cmd-extra/tests/commands/bits/into.rs b/crates/nu-cmd-extra/tests/commands/bits/format.rs similarity index 58% rename from crates/nu-cmd-extra/tests/commands/bits/into.rs rename to crates/nu-cmd-extra/tests/commands/bits/format.rs index b7e7700583..a06b6d8a68 100644 --- a/crates/nu-cmd-extra/tests/commands/bits/into.rs +++ b/crates/nu-cmd-extra/tests/commands/bits/format.rs @@ -2,12 +2,12 @@ use nu_test_support::nu; #[test] fn byte_stream_into_bits() { - let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits"); + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | format bits"); assert_eq!("00000001 00000010 00000011", result.out); } #[test] fn byte_stream_into_bits_is_stream() { - let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe"); + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | format bits | describe"); assert_eq!("string (stream)", result.out); } diff --git a/tests/repl/test_bits.rs b/tests/repl/test_bits.rs index 05c7ebad05..264c5e80ce 100644 --- a/tests/repl/test_bits.rs +++ b/tests/repl/test_bits.rs @@ -99,7 +99,7 @@ fn bits_shift_left_list() -> TestResult { #[test] fn bits_shift_left_binary1() -> TestResult { run_test( - "0x[01 30 80] | bits shl 3 | into bits", + "0x[01 30 80] | bits shl 3 | format bits", "00001001 10000100 00000000", ) } @@ -108,7 +108,7 @@ fn bits_shift_left_binary1() -> TestResult { fn bits_shift_left_binary2() -> TestResult { // Whole byte case run_test( - "0x[01 30 80] | bits shl 8 | into bits", + "0x[01 30 80] | bits shl 8 | format bits", "00110000 10000000 00000000", ) } @@ -117,7 +117,7 @@ fn bits_shift_left_binary2() -> TestResult { fn bits_shift_left_binary3() -> TestResult { // Compared to the int case this is made inclusive of the bit count run_test( - "0x[01 30 80] | bits shl 24 | into bits", + "0x[01 30 80] | bits shl 24 | format bits", "00000000 00000000 00000000", ) } @@ -126,7 +126,7 @@ fn bits_shift_left_binary3() -> TestResult { fn bits_shift_left_binary4() -> TestResult { // Shifting by both bytes and bits run_test( - "0x[01 30 80] | bits shl 15 | into bits", + "0x[01 30 80] | bits shl 15 | format bits", "01000000 00000000 00000000", ) } @@ -134,7 +134,7 @@ fn bits_shift_left_binary4() -> TestResult { #[test] fn bits_shift_left_binary_exceeding() -> TestResult { // Compared to the int case this is made inclusive of the bit count - fail_test("0x[01 30] | bits shl 17 | into bits", "") + fail_test("0x[01 30] | bits shl 17 | format bits", "") } #[test] @@ -185,7 +185,7 @@ fn bits_shift_right_list() -> TestResult { #[test] fn bits_shift_right_binary1() -> TestResult { run_test( - "0x[01 30 80] | bits shr 3 | into bits", + "0x[01 30 80] | bits shr 3 | format bits", "00000000 00100110 00010000", ) } @@ -194,7 +194,7 @@ fn bits_shift_right_binary1() -> TestResult { fn bits_shift_right_binary2() -> TestResult { // Whole byte case run_test( - "0x[01 30 80] | bits shr 8 | into bits", + "0x[01 30 80] | bits shr 8 | format bits", "00000000 00000001 00110000", ) } @@ -203,7 +203,7 @@ fn bits_shift_right_binary2() -> TestResult { fn bits_shift_right_binary3() -> TestResult { // Compared to the int case this is made inclusive of the bit count run_test( - "0x[01 30 80] | bits shr 24 | into bits", + "0x[01 30 80] | bits shr 24 | format bits", "00000000 00000000 00000000", ) } @@ -212,7 +212,7 @@ fn bits_shift_right_binary3() -> TestResult { fn bits_shift_right_binary4() -> TestResult { // Shifting by both bytes and bits run_test( - "0x[01 30 80] | bits shr 15 | into bits", + "0x[01 30 80] | bits shr 15 | format bits", "00000000 00000000 00000010", ) } @@ -220,7 +220,7 @@ fn bits_shift_right_binary4() -> TestResult { #[test] fn bits_shift_right_binary_exceeding() -> TestResult { // Compared to the int case this is made inclusive of the bit count - fail_test("0x[01 30] | bits shr 17 | into bits", "available bits (16)") + fail_test("0x[01 30] | bits shr 17 | format bits", "available bits (16)") } #[test] @@ -261,7 +261,7 @@ fn bits_rotate_left_exceeding2() -> TestResult { #[test] fn bits_rotate_left_binary1() -> TestResult { run_test( - "0x[01 30 80] | bits rol 3 | into bits", + "0x[01 30 80] | bits rol 3 | format bits", "00001001 10000100 00000000", ) } @@ -270,7 +270,7 @@ fn bits_rotate_left_binary1() -> TestResult { fn bits_rotate_left_binary2() -> TestResult { // Whole byte case run_test( - "0x[01 30 80] | bits rol 8 | into bits", + "0x[01 30 80] | bits rol 8 | format bits", "00110000 10000000 00000001", ) } @@ -279,7 +279,7 @@ fn bits_rotate_left_binary2() -> TestResult { fn bits_rotate_left_binary3() -> TestResult { // Compared to the int case this is made inclusive of the bit count run_test( - "0x[01 30 80] | bits rol 24 | into bits", + "0x[01 30 80] | bits rol 24 | format bits", "00000001 00110000 10000000", ) } @@ -288,7 +288,7 @@ fn bits_rotate_left_binary3() -> TestResult { fn bits_rotate_left_binary4() -> TestResult { // Shifting by both bytes and bits run_test( - "0x[01 30 80] | bits rol 15 | into bits", + "0x[01 30 80] | bits rol 15 | format bits", "01000000 00000000 10011000", ) } @@ -331,7 +331,7 @@ fn bits_rotate_right_exceeding2() -> TestResult { #[test] fn bits_rotate_right_binary1() -> TestResult { run_test( - "0x[01 30 80] | bits ror 3 | into bits", + "0x[01 30 80] | bits ror 3 | format bits", "00000000 00100110 00010000", ) } @@ -340,7 +340,7 @@ fn bits_rotate_right_binary1() -> TestResult { fn bits_rotate_right_binary2() -> TestResult { // Whole byte case run_test( - "0x[01 30 80] | bits ror 8 | into bits", + "0x[01 30 80] | bits ror 8 | format bits", "10000000 00000001 00110000", ) } @@ -349,7 +349,7 @@ fn bits_rotate_right_binary2() -> TestResult { fn bits_rotate_right_binary3() -> TestResult { // Compared to the int case this is made inclusive of the bit count run_test( - "0x[01 30 80] | bits ror 24 | into bits", + "0x[01 30 80] | bits ror 24 | format bits", "00000001 00110000 10000000", ) } @@ -358,7 +358,7 @@ fn bits_rotate_right_binary3() -> TestResult { fn bits_rotate_right_binary4() -> TestResult { // Shifting by both bytes and bits run_test( - "0x[01 30 80] | bits ror 15 | into bits", + "0x[01 30 80] | bits ror 15 | format bits", "01100001 00000000 00000010", ) }