1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3
4struct Arguments {
5 cell_paths: Option<Vec<CellPath>>,
6 compact: bool,
7 little_endian: bool,
8}
9
10impl CmdArgument for Arguments {
11 fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
12 self.cell_paths.take()
13 }
14}
15
16#[derive(Clone)]
17pub struct IntoBinary;
18
19impl Command for IntoBinary {
20 fn name(&self) -> &str {
21 "into binary"
22 }
23
24 fn signature(&self) -> Signature {
25 Signature::build("into binary")
26 .input_output_types(vec![
27 (Type::Binary, Type::Binary),
28 (Type::Int, Type::Binary),
29 (Type::Number, Type::Binary),
30 (Type::String, Type::Binary),
31 (Type::Bool, Type::Binary),
32 (Type::Filesize, Type::Binary),
33 (Type::Date, Type::Binary),
34 (Type::table(), Type::table()),
35 (Type::record(), Type::record()),
36 ])
37 .allow_variants_without_examples(true) .switch("compact", "output without padding zeros", Some('c'))
39 .named(
40 "endian",
41 SyntaxShape::String,
42 "byte encode endian. Does not affect string, date or binary. In containers, only individual elements are affected. Available options: native(default), little, big",
43 Some('e'),
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.get_flag::<Spanned<String>>(engine_state, stack, "endian")?;
161
162 let little_endian = if let Some(endian) = endian {
163 match endian.item.as_str() {
164 "native" => cfg!(target_endian = "little"),
165 "little" => true,
166 "big" => false,
167 _ => {
168 return Err(ShellError::TypeMismatch {
169 err_message: "Endian must be one of native, little, big".to_string(),
170 span: endian.span,
171 });
172 }
173 }
174 } else {
175 cfg!(target_endian = "little")
176 };
177
178 let args = Arguments {
179 cell_paths,
180 compact: call.has_flag(engine_state, stack, "compact")?,
181 little_endian,
182 };
183 operate(action, args, input, head, engine_state.signals())
184 }
185}
186
187fn action(input: &Value, args: &Arguments, span: Span) -> Value {
188 let value = match input {
189 Value::Binary { .. } => input.clone(),
190 Value::Int { val, .. } => Value::binary(
191 if args.little_endian {
192 val.to_le_bytes()
193 } else {
194 val.to_be_bytes()
195 }
196 .to_vec(),
197 span,
198 ),
199 Value::Float { val, .. } => Value::binary(
200 if args.little_endian {
201 val.to_le_bytes()
202 } else {
203 val.to_be_bytes()
204 }
205 .to_vec(),
206 span,
207 ),
208 Value::Filesize { val, .. } => Value::binary(
209 if args.little_endian {
210 val.get().to_le_bytes()
211 } else {
212 val.get().to_be_bytes()
213 }
214 .to_vec(),
215 span,
216 ),
217 Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span),
218 Value::Bool { val, .. } => Value::binary(
219 {
220 let as_int = i64::from(*val);
221 if args.little_endian {
222 as_int.to_le_bytes()
223 } else {
224 as_int.to_be_bytes()
225 }
226 .to_vec()
227 },
228 span,
229 ),
230 Value::Duration { val, .. } => Value::binary(
231 if args.little_endian {
232 val.to_le_bytes()
233 } else {
234 val.to_be_bytes()
235 }
236 .to_vec(),
237 span,
238 ),
239 Value::Date { val, .. } => {
240 Value::binary(val.format("%c").to_string().as_bytes().to_vec(), span)
241 }
242 Value::Error { .. } => input.clone(),
244 other => Value::error(
245 ShellError::OnlySupportsThisInputType {
246 exp_input_type: "int, float, filesize, string, date, duration, binary, or bool"
247 .into(),
248 wrong_type: other.get_type().to_string(),
249 dst_span: span,
250 src_span: other.span(),
251 },
252 span,
253 ),
254 };
255
256 if args.compact {
257 let val_span = value.span();
258 if let Value::Binary { val, .. } = value {
259 let val = if args.little_endian {
260 match val.iter().rposition(|&x| x != 0) {
261 Some(idx) => &val[..idx + 1],
262
263 None => &[0],
265 }
266 } else {
267 match val.iter().position(|&x| x != 0) {
268 Some(idx) => &val[idx..],
269 None => &[0],
270 }
271 };
272
273 Value::binary(val.to_vec(), val_span)
274 } else {
275 value
276 }
277 } else {
278 value
279 }
280}
281
282#[cfg(test)]
283mod test {
284 use rstest::rstest;
285
286 use super::*;
287
288 #[test]
289 fn test_examples() {
290 use crate::test_examples;
291
292 test_examples(IntoBinary {})
293 }
294
295 #[rstest]
296 #[case(vec![10], vec![10], vec![10])]
297 #[case(vec![10, 0, 0], vec![10], vec![10, 0, 0])]
298 #[case(vec![0, 0, 10], vec![0, 0, 10], vec![10])]
299 #[case(vec![0, 10, 0, 0], vec![0, 10], vec![10, 0, 0])]
300 fn test_compact(#[case] input: Vec<u8>, #[case] little: Vec<u8>, #[case] big: Vec<u8>) {
301 let s = Value::test_binary(input);
302 let actual = action(
303 &s,
304 &Arguments {
305 cell_paths: None,
306 compact: true,
307 little_endian: cfg!(target_endian = "little"),
308 },
309 Span::test_data(),
310 );
311 if cfg!(target_endian = "little") {
312 assert_eq!(actual, Value::test_binary(little));
313 } else {
314 assert_eq!(actual, Value::test_binary(big));
315 }
316 }
317}