nu_command/conversions/into/
binary.rs

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}
8
9impl CmdArgument for Arguments {
10    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
11        self.cell_paths.take()
12    }
13}
14
15#[derive(Clone)]
16pub struct IntoBinary;
17
18impl Command for IntoBinary {
19    fn name(&self) -> &str {
20        "into binary"
21    }
22
23    fn signature(&self) -> Signature {
24        Signature::build("into binary")
25            .input_output_types(vec![
26                (Type::Binary, Type::Binary),
27                (Type::Int, Type::Binary),
28                (Type::Number, Type::Binary),
29                (Type::String, Type::Binary),
30                (Type::Bool, Type::Binary),
31                (Type::Filesize, Type::Binary),
32                (Type::Date, Type::Binary),
33                (Type::table(), Type::table()),
34                (Type::record(), Type::record()),
35            ])
36            .allow_variants_without_examples(true) // TODO: supply exhaustive examples
37            .switch("compact", "output without padding zeros", Some('c'))
38            .rest(
39                "rest",
40                SyntaxShape::CellPath,
41                "For a data structure input, convert data at the given cell paths.",
42            )
43            .category(Category::Conversions)
44    }
45
46    fn description(&self) -> &str {
47        "Convert value to a binary primitive."
48    }
49
50    fn search_terms(&self) -> Vec<&str> {
51        vec!["convert", "bytes"]
52    }
53
54    fn run(
55        &self,
56        engine_state: &EngineState,
57        stack: &mut Stack,
58        call: &Call,
59        input: PipelineData,
60    ) -> Result<PipelineData, ShellError> {
61        into_binary(engine_state, stack, call, input)
62    }
63
64    fn examples(&self) -> Vec<Example> {
65        vec![
66            Example {
67                description: "convert string to a nushell binary primitive",
68                example: "'This is a string that is exactly 52 characters long.' | into binary",
69                result: Some(Value::binary(
70                    "This is a string that is exactly 52 characters long."
71                        .to_string()
72                        .as_bytes()
73                        .to_vec(),
74                    Span::test_data(),
75                )),
76            },
77            Example {
78                description: "convert a number to a nushell binary primitive",
79                example: "1 | into binary",
80                result: Some(Value::binary(
81                    i64::from(1).to_ne_bytes().to_vec(),
82                    Span::test_data(),
83                )),
84            },
85            Example {
86                description: "convert a boolean to a nushell binary primitive",
87                example: "true | into binary",
88                result: Some(Value::binary(
89                    i64::from(1).to_ne_bytes().to_vec(),
90                    Span::test_data(),
91                )),
92            },
93            Example {
94                description: "convert a filesize to a nushell binary primitive",
95                example: "ls | where name == LICENSE | get size | into binary",
96                result: None,
97            },
98            Example {
99                description: "convert a filepath to a nushell binary primitive",
100                example: "ls | where name == LICENSE | get name | path expand | into binary",
101                result: None,
102            },
103            Example {
104                description: "convert a float to a nushell binary primitive",
105                example: "1.234 | into binary",
106                result: Some(Value::binary(
107                    1.234f64.to_ne_bytes().to_vec(),
108                    Span::test_data(),
109                )),
110            },
111            Example {
112                description: "convert an int to a nushell binary primitive with compact enabled",
113                example: "10 | into binary --compact",
114                result: Some(Value::binary(vec![10], Span::test_data())),
115            },
116        ]
117    }
118}
119
120fn into_binary(
121    engine_state: &EngineState,
122    stack: &mut Stack,
123    call: &Call,
124    input: PipelineData,
125) -> Result<PipelineData, ShellError> {
126    let head = call.head;
127    let cell_paths = call.rest(engine_state, stack, 0)?;
128    let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
129
130    if let PipelineData::ByteStream(stream, metadata) = input {
131        // Just set the type - that should be good enough
132        Ok(PipelineData::ByteStream(
133            stream.with_type(ByteStreamType::Binary),
134            metadata,
135        ))
136    } else {
137        let args = Arguments {
138            cell_paths,
139            compact: call.has_flag(engine_state, stack, "compact")?,
140        };
141        operate(action, args, input, head, engine_state.signals())
142    }
143}
144
145fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
146    let value = match input {
147        Value::Binary { .. } => input.clone(),
148        Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
149        Value::Float { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
150        Value::Filesize { val, .. } => Value::binary(val.get().to_ne_bytes().to_vec(), span),
151        Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span),
152        Value::Bool { val, .. } => Value::binary(i64::from(*val).to_ne_bytes().to_vec(), span),
153        Value::Duration { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
154        Value::Date { val, .. } => {
155            Value::binary(val.format("%c").to_string().as_bytes().to_vec(), span)
156        }
157        // Propagate errors by explicitly matching them before the final case.
158        Value::Error { .. } => input.clone(),
159        other => Value::error(
160            ShellError::OnlySupportsThisInputType {
161                exp_input_type: "int, float, filesize, string, date, duration, binary, or bool"
162                    .into(),
163                wrong_type: other.get_type().to_string(),
164                dst_span: span,
165                src_span: other.span(),
166            },
167            span,
168        ),
169    };
170
171    if _args.compact {
172        let val_span = value.span();
173        if let Value::Binary { val, .. } = value {
174            let val = if cfg!(target_endian = "little") {
175                match val.iter().rposition(|&x| x != 0) {
176                    Some(idx) => &val[..idx + 1],
177
178                    // all 0s should just return a single 0 byte
179                    None => &[0],
180                }
181            } else {
182                match val.iter().position(|&x| x != 0) {
183                    Some(idx) => &val[idx..],
184                    None => &[0],
185                }
186            };
187
188            Value::binary(val.to_vec(), val_span)
189        } else {
190            value
191        }
192    } else {
193        value
194    }
195}
196
197#[cfg(test)]
198mod test {
199    use rstest::rstest;
200
201    use super::*;
202
203    #[test]
204    fn test_examples() {
205        use crate::test_examples;
206
207        test_examples(IntoBinary {})
208    }
209
210    #[rstest]
211    #[case(vec![10], vec![10], vec![10])]
212    #[case(vec![10, 0, 0], vec![10], vec![10, 0, 0])]
213    #[case(vec![0, 0, 10], vec![0, 0, 10], vec![10])]
214    #[case(vec![0, 10, 0, 0], vec![0, 10], vec![10, 0, 0])]
215    fn test_compact(#[case] input: Vec<u8>, #[case] little: Vec<u8>, #[case] big: Vec<u8>) {
216        let s = Value::test_binary(input);
217        let actual = action(
218            &s,
219            &Arguments {
220                cell_paths: None,
221                compact: true,
222            },
223            Span::test_data(),
224        );
225        if cfg!(target_endian = "little") {
226            assert_eq!(actual, Value::test_binary(little));
227        } else {
228            assert_eq!(actual, Value::test_binary(big));
229        }
230    }
231}