Fix 9156 endian consistency (#9873)

- fixed #9156

# Description
I'm trying to fix the problems mentioned in the issue. It's my first
attempt in Rust. Please let me know if there are any problems.

# User-Facing Changes
- The `--little-endian` option dropped, replaced with `--endian`.
- Add the `--compact` option to the `into binary` command.
- `into int` accepts binary input
This commit is contained in:
Herobs 2023-08-24 20:08:58 +08:00 committed by GitHub
parent d4eeef4bd1
commit a785e64bc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 29 deletions

View File

@ -9,6 +9,7 @@ use nu_protocol::{
pub struct Arguments { pub struct Arguments {
cell_paths: Option<Vec<CellPath>>, cell_paths: Option<Vec<CellPath>>,
compact: bool,
} }
impl CmdArgument for Arguments { impl CmdArgument for Arguments {
@ -39,6 +40,7 @@ impl Command for SubCommand {
(Type::Record(vec![]), Type::Record(vec![])), (Type::Record(vec![]), Type::Record(vec![])),
]) ])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples .allow_variants_without_examples(true) // TODO: supply exhaustive examples
.switch("compact", "output without padding zeros", Some('c'))
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
@ -82,7 +84,7 @@ impl Command for SubCommand {
description: "convert a number to a nushell binary primitive", description: "convert a number to a nushell binary primitive",
example: "1 | into binary", example: "1 | into binary",
result: Some(Value::Binary { result: Some(Value::Binary {
val: i64::from(1).to_le_bytes().to_vec(), val: i64::from(1).to_ne_bytes().to_vec(),
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
@ -90,7 +92,7 @@ impl Command for SubCommand {
description: "convert a boolean to a nushell binary primitive", description: "convert a boolean to a nushell binary primitive",
example: "true | into binary", example: "true | into binary",
result: Some(Value::Binary { result: Some(Value::Binary {
val: i64::from(1).to_le_bytes().to_vec(), val: i64::from(1).to_ne_bytes().to_vec(),
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
@ -108,7 +110,16 @@ impl Command for SubCommand {
description: "convert a decimal to a nushell binary primitive", description: "convert a decimal to a nushell binary primitive",
example: "1.234 | into binary", example: "1.234 | into binary",
result: Some(Value::Binary { result: Some(Value::Binary {
val: 1.234f64.to_le_bytes().to_vec(), val: 1.234f64.to_ne_bytes().to_vec(),
span: Span::test_data(),
}),
},
Example {
description:
"convert an integer to a nushell binary primitive with compact enabled",
example: "10 | into binary --compact",
result: Some(Value::Binary {
val: vec![10],
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
@ -145,41 +156,28 @@ fn into_binary(
.into_pipeline_data()) .into_pipeline_data())
} }
_ => { _ => {
let args = Arguments { cell_paths }; let args = Arguments {
cell_paths,
compact: call.has_flag("compact"),
};
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
} }
} }
fn int_to_endian(n: i64) -> Vec<u8> {
if cfg!(target_endian = "little") {
n.to_le_bytes().to_vec()
} else {
n.to_be_bytes().to_vec()
}
}
fn float_to_endian(n: f64) -> Vec<u8> {
if cfg!(target_endian = "little") {
n.to_le_bytes().to_vec()
} else {
n.to_be_bytes().to_vec()
}
}
pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value { pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
match input { let value = match input {
Value::Binary { .. } => input.clone(), Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::Binary { Value::Int { val, .. } => Value::Binary {
val: int_to_endian(*val), val: val.to_ne_bytes().to_vec(),
span, span,
}, },
Value::Float { val, .. } => Value::Binary { Value::Float { val, .. } => Value::Binary {
val: float_to_endian(*val), val: val.to_ne_bytes().to_vec(),
span, span,
}, },
Value::Filesize { val, .. } => Value::Binary { Value::Filesize { val, .. } => Value::Binary {
val: int_to_endian(*val), val: val.to_ne_bytes().to_vec(),
span, span,
}, },
Value::String { val, .. } => Value::Binary { Value::String { val, .. } => Value::Binary {
@ -187,11 +185,11 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
span, span,
}, },
Value::Bool { val, .. } => Value::Binary { Value::Bool { val, .. } => Value::Binary {
val: int_to_endian(i64::from(*val)), val: i64::from(*val).to_ne_bytes().to_vec(),
span, span,
}, },
Value::Duration { val, .. } => Value::Binary { Value::Duration { val, .. } => Value::Binary {
val: int_to_endian(*val), val: val.to_ne_bytes().to_vec(),
span, span,
}, },
Value::Date { val, .. } => Value::Binary { Value::Date { val, .. } => Value::Binary {
@ -209,11 +207,38 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
src_span: other.expect_span(), src_span: other.expect_span(),
}), }),
}, },
};
if _args.compact {
if let Value::Binary { val, span } = value {
let val = if cfg!(target_endian = "little") {
match val.iter().rposition(|&x| x != 0) {
Some(idx) => &val[..idx + 1],
None => &val,
}
} else {
match val.iter().position(|&x| x != 0) {
Some(idx) => &val[idx..],
None => &val,
}
};
Value::Binary {
val: val.to_vec(),
span,
}
} else {
value
}
} else {
value
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use rstest::rstest;
use super::*; use super::*;
#[test] #[test]
@ -222,4 +247,26 @@ mod test {
test_examples(SubCommand {}) test_examples(SubCommand {})
} }
#[rstest]
#[case(vec![10], vec![10], vec![10])]
#[case(vec![10, 0, 0], vec![10], vec![10, 0, 0])]
#[case(vec![0, 0, 10], vec![0, 0, 10], vec![10])]
#[case(vec![0, 10, 0, 0], vec![0, 10], vec![10, 0, 0])]
fn test_compact(#[case] input: Vec<u8>, #[case] little: Vec<u8>, #[case] big: Vec<u8>) {
let s = Value::test_binary(input);
let actual = action(
&s,
&Arguments {
cell_paths: None,
compact: true,
},
Span::test_data(),
);
if cfg!(target_endian = "little") {
assert_eq!(actual, Value::test_binary(little));
} else {
assert_eq!(actual, Value::test_binary(big));
}
}
} }

View File

@ -38,6 +38,7 @@ impl Command for SubCommand {
(Type::Date, Type::Int), (Type::Date, Type::Int),
(Type::Duration, Type::Int), (Type::Duration, Type::Int),
(Type::Filesize, Type::Int), (Type::Filesize, Type::Int),
(Type::Binary, Type::Int),
(Type::Table(vec![]), Type::Table(vec![])), (Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])), (Type::Record(vec![]), Type::Record(vec![])),
( (
@ -72,7 +73,12 @@ impl Command for SubCommand {
]) ])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.named("radix", SyntaxShape::Number, "radix of integer", Some('r')) .named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
.switch("little-endian", "use little-endian byte decoding", None) .named(
"endian",
SyntaxShape::String,
"byte encode endian, available options: native(default), little, big",
Some('e'),
)
.rest( .rest(
"rest", "rest",
SyntaxShape::CellPath, SyntaxShape::CellPath,
@ -113,9 +119,27 @@ impl Command for SubCommand {
Some(_) => 10, Some(_) => 10,
None => 10, None => 10,
}; };
let endian = call.get_flag::<Value>(engine_state, stack, "endian")?;
let little_endian = match endian {
Some(Value::String { val, span }) => match val.as_str() {
"native" => cfg!(target_endian = "little"),
"little" => true,
"big" => false,
_ => {
return Err(ShellError::TypeMismatch {
err_message: "Endian must be one of native, little, big".to_string(),
span,
})
}
},
Some(_) => false,
None => cfg!(target_endian = "little"),
};
let args = Arguments { let args = Arguments {
radix, radix,
little_endian: call.has_flag("little-endian"), little_endian,
cell_paths, cell_paths,
}; };
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())

View File

@ -0,0 +1,25 @@
use nu_test_support::nu;
#[test]
fn convert_back_and_forth() {
let actual = nu!(r#"1 | into binary | into int"#);
assert_eq!(actual.out, "1");
}
#[test]
fn convert_into_int_little_endian() {
let actual = nu!(r#"0x[01 00 00 00 00 00 00 00] | into int --endian little"#);
assert_eq!(actual.out, "1");
let actual = nu!(r#"0x[00 00 00 00 00 00 00 01] | into int --endian little"#);
assert_eq!(actual.out, "72057594037927936");
}
#[test]
fn convert_into_int_big_endian() {
let actual = nu!(r#"0x[00 00 00 00 00 00 00 01] | into int --endian big"#);
assert_eq!(actual.out, "1");
let actual = nu!(r#"0x[01 00 00 00 00 00 00 00] | into int --endian big"#);
assert_eq!(actual.out, "72057594037927936");
}

View File

@ -0,0 +1 @@
mod int;

View File

@ -0,0 +1 @@
mod into;

View File

@ -8,6 +8,7 @@ mod cal;
mod cd; mod cd;
mod compact; mod compact;
mod continue_; mod continue_;
mod conversions;
mod cp; mod cp;
mod date; mod date;
mod def; mod def;

View File

@ -8,7 +8,7 @@ fn binary_skip() {
open sample_data.ods --raw | open sample_data.ods --raw |
skip 2 | skip 2 |
take 2 | take 2 |
into int into int --endian big
"# "#
)); ));