1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3use nu_heavy_utils::Endian;
4
5struct Arguments {
6 cell_paths: Option<Vec<CellPath>>,
7 compact: bool,
8 endian: Endian,
9}
10
11impl CmdArgument for Arguments {
12 fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
13 self.cell_paths.take()
14 }
15}
16
17#[derive(Clone)]
18pub struct IntoBinary;
19
20impl Command for IntoBinary {
21 fn name(&self) -> &str {
22 "into binary"
23 }
24
25 fn signature(&self) -> Signature {
26 Signature::build("into binary")
27 .input_output_types(vec![
28 (Type::Binary, Type::Binary),
29 (Type::Int, Type::Binary),
30 (Type::Number, Type::Binary),
31 (Type::String, Type::Binary),
32 (Type::Bool, Type::Binary),
33 (Type::Filesize, Type::Binary),
34 (Type::Date, Type::Binary),
35 (Type::table(), Type::table()),
36 (Type::record(), Type::record()),
37 ])
38 .allow_variants_without_examples(true) .switch("compact", "Output without padding zeros.", Some('c'))
40 .param(Endian::flag().desc(
41 "Byte encode endian. Does not affect string, date or binary. \
42 In containers, only individual elements are affected. \
43 Available options: native(default), little, big.",
44 ))
45 .rest(
46 "rest",
47 SyntaxShape::CellPath,
48 "For a data structure input, convert data at the given cell paths.",
49 )
50 .category(Category::Conversions)
51 }
52
53 fn description(&self) -> &str {
54 "Convert value to a binary primitive."
55 }
56
57 fn search_terms(&self) -> Vec<&str> {
58 vec!["convert", "bytes"]
59 }
60
61 fn run(
62 &self,
63 engine_state: &EngineState,
64 stack: &mut Stack,
65 call: &Call,
66 input: PipelineData,
67 ) -> Result<PipelineData, ShellError> {
68 into_binary(engine_state, stack, call, input)
69 }
70
71 fn examples(&self) -> Vec<Example<'_>> {
72 vec![
73 Example {
74 description: "convert string to a nushell binary primitive.",
75 example: "'This is a string that is exactly 52 characters long.' | into binary",
76 result: Some(Value::binary(
77 "This is a string that is exactly 52 characters long."
78 .to_string()
79 .as_bytes()
80 .to_vec(),
81 Span::test_data(),
82 )),
83 },
84 Example {
85 description: "convert a number to a nushell binary primitive.",
86 example: "1 | into binary",
87 result: Some(Value::binary(
88 i64::from(1).to_ne_bytes().to_vec(),
89 Span::test_data(),
90 )),
91 },
92 Example {
93 description: "convert a number to a nushell binary primitive (big endian).",
94 example: "258 | into binary --endian big",
95 result: Some(Value::binary(
96 i64::from(258).to_be_bytes().to_vec(),
97 Span::test_data(),
98 )),
99 },
100 Example {
101 description: "convert a number to a nushell binary primitive (little endian).",
102 example: "258 | into binary --endian little",
103 result: Some(Value::binary(
104 i64::from(258).to_le_bytes().to_vec(),
105 Span::test_data(),
106 )),
107 },
108 Example {
109 description: "convert a boolean to a nushell binary primitive.",
110 example: "true | into binary",
111 result: Some(Value::binary(
112 i64::from(1).to_ne_bytes().to_vec(),
113 Span::test_data(),
114 )),
115 },
116 Example {
117 description: "convert a filesize to a nushell binary primitive.",
118 example: "ls | where name == LICENSE | get size | into binary",
119 result: None,
120 },
121 Example {
122 description: "convert a filepath to a nushell binary primitive.",
123 example: "ls | where name == LICENSE | get name | path expand | into binary",
124 result: None,
125 },
126 Example {
127 description: "convert a float to a nushell binary primitive.",
128 example: "1.234 | into binary",
129 result: Some(Value::binary(
130 1.234f64.to_ne_bytes().to_vec(),
131 Span::test_data(),
132 )),
133 },
134 Example {
135 description: "convert an int to a nushell binary primitive with compact enabled.",
136 example: "10 | into binary --compact",
137 result: Some(Value::binary(vec![10], Span::test_data())),
138 },
139 ]
140 }
141}
142
143fn into_binary(
144 engine_state: &EngineState,
145 stack: &mut Stack,
146 call: &Call,
147 input: PipelineData,
148) -> Result<PipelineData, ShellError> {
149 let head = call.head;
150 let cell_paths = call.rest(engine_state, stack, 0)?;
151 let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
152
153 if let PipelineData::ByteStream(stream, metadata) = input {
154 Ok(PipelineData::byte_stream(
156 stream.with_type(ByteStreamType::Binary),
157 metadata,
158 ))
159 } else {
160 let endian = call
161 .get_flag::<Endian>(engine_state, stack, "endian")?
162 .unwrap_or_default();
163
164 let args = Arguments {
165 cell_paths,
166 compact: call.has_flag(engine_state, stack, "compact")?,
167 endian,
168 };
169 operate(action, args, input, head, engine_state.signals())
170 }
171}
172
173fn action(input: &Value, args: &Arguments, span: Span) -> Value {
174 let value = match input {
175 Value::Binary { .. } => input.clone(),
176 Value::Int { val, .. } => Value::binary(
177 match args.endian {
178 Endian::Little => val.to_le_bytes(),
179 Endian::Big => val.to_be_bytes(),
180 }
181 .to_vec(),
182 span,
183 ),
184 Value::Float { val, .. } => Value::binary(
185 match args.endian {
186 Endian::Little => val.to_le_bytes(),
187 Endian::Big => val.to_be_bytes(),
188 }
189 .to_vec(),
190 span,
191 ),
192 Value::Filesize { val, .. } => Value::binary(
193 match args.endian {
194 Endian::Little => val.get().to_le_bytes(),
195 Endian::Big => val.get().to_be_bytes(),
196 }
197 .to_vec(),
198 span,
199 ),
200 Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span),
201 Value::Bool { val, .. } => Value::binary(
202 {
203 let as_int = i64::from(*val);
204 match args.endian {
205 Endian::Little => as_int.to_le_bytes(),
206 Endian::Big => as_int.to_be_bytes(),
207 }
208 .to_vec()
209 },
210 span,
211 ),
212 Value::Duration { val, .. } => Value::binary(
213 match args.endian {
214 Endian::Little => val.to_le_bytes(),
215 Endian::Big => val.to_be_bytes(),
216 }
217 .to_vec(),
218 span,
219 ),
220 Value::Date { val, .. } => {
221 Value::binary(val.format("%c").to_string().as_bytes().to_vec(), span)
222 }
223 Value::Error { .. } => input.clone(),
225 other => Value::error(
226 ShellError::OnlySupportsThisInputType {
227 exp_input_type: "int, float, filesize, string, date, duration, binary, or bool"
228 .into(),
229 wrong_type: other.get_type().to_string(),
230 dst_span: span,
231 src_span: other.span(),
232 },
233 span,
234 ),
235 };
236
237 if args.compact {
238 let val_span = value.span();
239 if let Value::Binary { val, .. } = value {
240 let val = match args.endian {
241 Endian::Little => {
242 match val.iter().rposition(|&x| x != 0) {
243 Some(idx) => &val[..idx + 1],
244
245 None => &[0],
247 }
248 }
249 Endian::Big => match val.iter().position(|&x| x != 0) {
250 Some(idx) => &val[idx..],
251 None => &[0],
252 },
253 };
254
255 Value::binary(val.to_vec(), val_span)
256 } else {
257 value
258 }
259 } else {
260 value
261 }
262}
263
264#[cfg(test)]
265mod test {
266 use rstest::rstest;
267
268 use super::*;
269
270 #[test]
271 fn test_examples() -> nu_test_support::Result {
272 nu_test_support::test().examples(IntoBinary)
273 }
274
275 #[rstest]
276 #[case(vec![10], vec![10], vec![10])]
277 #[case(vec![10, 0, 0], vec![10], vec![10, 0, 0])]
278 #[case(vec![0, 0, 10], vec![0, 0, 10], vec![10])]
279 #[case(vec![0, 10, 0, 0], vec![0, 10], vec![10, 0, 0])]
280 fn test_compact(#[case] input: Vec<u8>, #[case] little: Vec<u8>, #[case] big: Vec<u8>) {
281 let s = Value::test_binary(input);
282 let actual = action(
283 &s,
284 &Arguments {
285 cell_paths: None,
286 compact: true,
287 endian: Endian::NATIVE,
288 },
289 Span::test_data(),
290 );
291 if cfg!(target_endian = "little") {
292 assert_eq!(actual, Value::test_binary(little));
293 } else {
294 assert_eq!(actual, Value::test_binary(big));
295 }
296 }
297}