Skip to main content

nu_command/conversions/into/
binary.rs

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) // TODO: supply exhaustive examples
39            .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        // Just set the type - that should be good enough
155        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        // Propagate errors by explicitly matching them before the final case.
224        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                        // all 0s should just return a single 0 byte
246                        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}