Skip to main content

nu_command/bytes/
index_of.rs

1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3
4struct Arguments {
5    pattern: Vec<u8>,
6    end: bool,
7    all: bool,
8    cell_paths: Option<Vec<CellPath>>,
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 BytesIndexOf;
19
20impl Command for BytesIndexOf {
21    fn name(&self) -> &str {
22        "bytes index-of"
23    }
24
25    fn signature(&self) -> Signature {
26        Signature::build("bytes index-of")
27            .input_output_types(vec![
28                (Type::Binary, Type::Any),
29                // FIXME: this shouldn't be needed, cell paths should work with the two
30                // above
31                (Type::table(), Type::table()),
32                (Type::record(), Type::record()),
33            ])
34            .allow_variants_without_examples(true)
35            .required(
36                "pattern",
37                SyntaxShape::Binary,
38                "The pattern to find index of.",
39            )
40            .rest(
41                "rest",
42                SyntaxShape::CellPath,
43                "For a data structure input, find the indexes at the given cell paths.",
44            )
45            .switch("all", "Returns all matched indices.", Some('a'))
46            .switch("end", "Search from the end of the binary.", Some('e'))
47            .category(Category::Bytes)
48    }
49
50    fn description(&self) -> &str {
51        "Returns start index of first occurrence of pattern in bytes, or -1 if no match."
52    }
53
54    fn search_terms(&self) -> Vec<&str> {
55        vec!["pattern", "match", "find", "search"]
56    }
57
58    fn run(
59        &self,
60        engine_state: &EngineState,
61        stack: &mut Stack,
62        call: &Call,
63        input: PipelineData,
64    ) -> Result<PipelineData, ShellError> {
65        let pattern: Spanned<Vec<u8>> = call.req(engine_state, stack, 0)?;
66        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
67        let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
68
69        if pattern.item.is_empty() {
70            return Err(ShellError::TypeMismatch {
71                err_message: "the pattern to find cannot be empty".to_string(),
72                span: pattern.span,
73            });
74        }
75
76        let arg = Arguments {
77            pattern: pattern.item,
78            end: call.has_flag(engine_state, stack, "end")?,
79            all: call.has_flag(engine_state, stack, "all")?,
80            cell_paths,
81        };
82
83        operate(index_of, arg, input, call.head, engine_state.signals())
84    }
85
86    fn examples(&self) -> Vec<Example<'_>> {
87        vec![
88            Example {
89                description: "Returns index of pattern in bytes.",
90                example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
91                result: Some(Value::test_int(1)),
92            },
93            Example {
94                description: "Returns index of pattern, search from end.",
95                example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of --end 0x[44 55]",
96                result: Some(Value::test_int(6)),
97            },
98            Example {
99                description: "Returns all matched index.",
100                example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of --all 0x[33 44]",
101                result: Some(Value::test_list(vec![
102                    Value::test_int(0),
103                    Value::test_int(5),
104                    Value::test_int(7),
105                ])),
106            },
107            Example {
108                description: "Returns all matched index, searching from end.",
109                example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of --all --end 0x[33 44]",
110                result: Some(Value::test_list(vec![
111                    Value::test_int(7),
112                    Value::test_int(5),
113                    Value::test_int(0),
114                ])),
115            },
116            Example {
117                description: "Returns index of pattern for specific column.",
118                example: " [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC",
119                result: Some(Value::test_list(vec![Value::test_record(record! {
120                    "ColA" => Value::test_int(0),
121                    "ColB" => Value::binary(vec![0x14, 0x15, 0x16], Span::test_data()),
122                    "ColC" => Value::test_int(-1),
123                })])),
124            },
125        ]
126    }
127}
128
129fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
130    let val_span = val.span();
131    match val {
132        Value::Binary { val, .. } => index_of_impl(val, args, val_span),
133        // Propagate errors by explicitly matching them before the final case.
134        Value::Error { .. } => val.clone(),
135        other => Value::error(
136            ShellError::OnlySupportsThisInputType {
137                exp_input_type: "binary".into(),
138                wrong_type: other.get_type().to_string(),
139                dst_span: span,
140                src_span: other.span(),
141            },
142            span,
143        ),
144    }
145}
146
147fn index_of_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
148    if arg.all {
149        search_all_index(input, &arg.pattern, arg.end, span)
150    } else {
151        let mut iter = input.windows(arg.pattern.len());
152
153        if arg.end {
154            Value::int(
155                iter.rev()
156                    .position(|sub_bytes| sub_bytes == arg.pattern)
157                    .map(|x| (input.len() - arg.pattern.len() - x) as i64)
158                    .unwrap_or(-1),
159                span,
160            )
161        } else {
162            Value::int(
163                iter.position(|sub_bytes| sub_bytes == arg.pattern)
164                    .map(|x| x as i64)
165                    .unwrap_or(-1),
166                span,
167            )
168        }
169    }
170}
171
172fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
173    let mut result = vec![];
174    if from_end {
175        let (mut left, mut right) = (
176            input.len() as isize - pattern.len() as isize,
177            input.len() as isize,
178        );
179        while left >= 0 {
180            if &input[left as usize..right as usize] == pattern {
181                result.push(Value::int(left as i64, span));
182                left -= pattern.len() as isize;
183                right -= pattern.len() as isize;
184            } else {
185                left -= 1;
186                right -= 1;
187            }
188        }
189        Value::list(result, span)
190    } else {
191        // doing find stuff.
192        let (mut left, mut right) = (0, pattern.len());
193        let input_len = input.len();
194        let pattern_len = pattern.len();
195        while right <= input_len {
196            if &input[left..right] == pattern {
197                result.push(Value::int(left as i64, span));
198                left += pattern_len;
199                right += pattern_len;
200            } else {
201                left += 1;
202                right += 1;
203            }
204        }
205
206        Value::list(result, span)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_examples() -> nu_test_support::Result {
216        nu_test_support::test().examples(BytesIndexOf)
217    }
218}