nu_command/bytes/
ends_with.rs1use 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 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 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}