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 index", 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: 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        let arg = Arguments {
69            pattern,
70            end: call.has_flag(engine_state, stack, "end")?,
71            all: call.has_flag(engine_state, stack, "all")?,
72            cell_paths,
73        };
74        operate(index_of, arg, input, call.head, engine_state.signals())
75    }
76
77    fn examples(&self) -> Vec<Example> {
78        vec![
79            Example {
80                description: "Returns index of pattern in bytes",
81                example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
82                result: Some(Value::test_int(1)),
83            },
84            Example {
85                description: "Returns index of pattern, search from end",
86                example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of --end 0x[44 55]",
87                result: Some(Value::test_int(6)),
88            },
89            Example {
90                description: "Returns all matched index",
91                example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of --all 0x[33 44]",
92                result: Some(Value::test_list(vec![
93                    Value::test_int(0),
94                    Value::test_int(5),
95                    Value::test_int(7),
96                ])),
97            },
98            Example {
99                description: "Returns all matched index, searching from end",
100                example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of --all --end 0x[33 44]",
101                result: Some(Value::test_list(vec![
102                    Value::test_int(7),
103                    Value::test_int(5),
104                    Value::test_int(0),
105                ])),
106            },
107            Example {
108                description: "Returns index of pattern for specific column",
109                example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
110                result: Some(Value::test_list(vec![Value::test_record(record! {
111                    "ColA" => Value::test_int(0),
112                    "ColB" => Value::binary(vec![0x14, 0x15, 0x16], Span::test_data()),
113                    "ColC" => Value::test_int(-1),
114                })])),
115            },
116        ]
117    }
118}
119
120fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
121    let val_span = val.span();
122    match val {
123        Value::Binary { val, .. } => index_of_impl(val, args, val_span),
124        // Propagate errors by explicitly matching them before the final case.
125        Value::Error { .. } => val.clone(),
126        other => Value::error(
127            ShellError::OnlySupportsThisInputType {
128                exp_input_type: "binary".into(),
129                wrong_type: other.get_type().to_string(),
130                dst_span: span,
131                src_span: other.span(),
132            },
133            span,
134        ),
135    }
136}
137
138fn index_of_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
139    if arg.all {
140        search_all_index(input, &arg.pattern, arg.end, span)
141    } else {
142        let mut iter = input.windows(arg.pattern.len());
143
144        if arg.end {
145            Value::int(
146                iter.rev()
147                    .position(|sub_bytes| sub_bytes == arg.pattern)
148                    .map(|x| (input.len() - arg.pattern.len() - x) as i64)
149                    .unwrap_or(-1),
150                span,
151            )
152        } else {
153            Value::int(
154                iter.position(|sub_bytes| sub_bytes == arg.pattern)
155                    .map(|x| x as i64)
156                    .unwrap_or(-1),
157                span,
158            )
159        }
160    }
161}
162
163fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
164    let mut result = vec![];
165    if from_end {
166        let (mut left, mut right) = (
167            input.len() as isize - pattern.len() as isize,
168            input.len() as isize,
169        );
170        while left >= 0 {
171            if &input[left as usize..right as usize] == pattern {
172                result.push(Value::int(left as i64, span));
173                left -= pattern.len() as isize;
174                right -= pattern.len() as isize;
175            } else {
176                left -= 1;
177                right -= 1;
178            }
179        }
180        Value::list(result, span)
181    } else {
182        // doing find stuff.
183        let (mut left, mut right) = (0, pattern.len());
184        let input_len = input.len();
185        let pattern_len = pattern.len();
186        while right <= input_len {
187            if &input[left..right] == pattern {
188                result.push(Value::int(left as i64, span));
189                left += pattern_len;
190                right += pattern_len;
191            } else {
192                left += 1;
193                right += 1;
194            }
195        }
196
197        Value::list(result, span)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_examples() {
207        use crate::test_examples;
208
209        test_examples(BytesIndexOf {})
210    }
211}