nu_command/bytes/
ends_with.rs

1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3use nu_protocol::shell_error::io::IoError;
4use std::{
5    collections::VecDeque,
6    io::{self, BufRead},
7};
8
9struct Arguments {
10    pattern: Vec<u8>,
11    cell_paths: Option<Vec<CellPath>>,
12}
13
14impl CmdArgument for Arguments {
15    fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
16        self.cell_paths.take()
17    }
18}
19
20#[derive(Clone)]
21
22pub struct BytesEndsWith;
23
24impl Command for BytesEndsWith {
25    fn name(&self) -> &str {
26        "bytes ends-with"
27    }
28
29    fn signature(&self) -> Signature {
30        Signature::build("bytes ends-with")
31            .input_output_types(vec![(Type::Binary, Type::Bool),
32                (Type::table(), Type::table()),
33                (Type::record(), Type::record()),
34            ])
35            .allow_variants_without_examples(true)
36            .required("pattern", SyntaxShape::Binary, "The pattern to match.")
37            .rest(
38                "rest",
39                SyntaxShape::CellPath,
40                "For a data structure input, check if bytes at the given cell paths end with the pattern.",
41            )
42            .category(Category::Bytes)
43    }
44
45    fn description(&self) -> &str {
46        "Check if bytes ends with a pattern."
47    }
48
49    fn search_terms(&self) -> Vec<&str> {
50        vec!["pattern", "match", "find", "search"]
51    }
52
53    fn run(
54        &self,
55        engine_state: &EngineState,
56        stack: &mut Stack,
57        call: &Call,
58        input: PipelineData,
59    ) -> Result<PipelineData, ShellError> {
60        let head = call.head;
61        let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
62        let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
63        let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
64
65        if let PipelineData::ByteStream(stream, ..) = input {
66            let span = stream.span();
67            if pattern.is_empty() {
68                return Ok(Value::bool(true, head).into_pipeline_data());
69            }
70            let Some(mut reader) = stream.reader() else {
71                return Ok(Value::bool(false, head).into_pipeline_data());
72            };
73            let cap = pattern.len();
74            let mut end = VecDeque::<u8>::with_capacity(cap);
75            loop {
76                let buf = match reader.fill_buf() {
77                    Ok(&[]) => break,
78                    Ok(buf) => buf,
79                    Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
80                    Err(e) => return Err(IoError::new(e, span, None).into()),
81                };
82                let len = buf.len();
83                if len >= cap {
84                    end.clear();
85                    end.extend(&buf[(len - cap)..])
86                } else {
87                    let new_len = len + end.len();
88                    if new_len > cap {
89                        // The `drain` below will panic if `(new_len - cap) > end.len()`.
90                        // But this cannot happen since we know `len < cap` (as checked above):
91                        //   (len + end.len() - cap) > end.len()
92                        //   => (len - cap) > 0
93                        //   => len > cap
94                        end.drain(..(new_len - cap));
95                    }
96                    end.extend(buf);
97                }
98                reader.consume(len);
99            }
100            Ok(Value::bool(end == pattern, head).into_pipeline_data())
101        } else {
102            let arg = Arguments {
103                pattern,
104                cell_paths,
105            };
106            operate(ends_with, arg, input, head, engine_state.signals())
107        }
108    }
109
110    fn examples(&self) -> Vec<Example> {
111        vec![
112            Example {
113                description: "Checks if binary ends with `0x[AA]`",
114                example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
115                result: Some(Value::test_bool(true)),
116            },
117            Example {
118                description: "Checks if binary ends with `0x[FF AA AA]`",
119                example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
120                result: Some(Value::test_bool(true)),
121            },
122            Example {
123                description: "Checks if binary ends with `0x[11]`",
124                example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
125                result: Some(Value::test_bool(false)),
126            },
127        ]
128    }
129}
130
131fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
132    let val_span = val.span();
133    match val {
134        Value::Binary { val, .. } => Value::bool(val.ends_with(&args.pattern), val_span),
135        // Propagate errors by explicitly matching them before the final case.
136        Value::Error { .. } => val.clone(),
137        other => Value::error(
138            ShellError::OnlySupportsThisInputType {
139                exp_input_type: "binary".into(),
140                wrong_type: other.get_type().to_string(),
141                dst_span: span,
142                src_span: other.span(),
143            },
144            span,
145        ),
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_examples() {
155        use crate::test_examples;
156
157        test_examples(BytesEndsWith {})
158    }
159}