rust_expect/expect/
buffer.rs1use std::collections::VecDeque;
7use std::fmt;
8
9pub const DEFAULT_CAPACITY: usize = 1024 * 1024;
11
12#[derive(Clone)]
17pub struct RingBuffer {
18 data: VecDeque<u8>,
20 max_size: usize,
22 total_written: usize,
24 bytes_discarded: usize,
26}
27
28impl RingBuffer {
29 #[must_use]
31 pub fn new(max_size: usize) -> Self {
32 Self {
33 data: VecDeque::with_capacity(max_size.min(DEFAULT_CAPACITY)),
34 max_size,
35 total_written: 0,
36 bytes_discarded: 0,
37 }
38 }
39
40 #[must_use]
42 pub fn with_default_capacity() -> Self {
43 Self::new(DEFAULT_CAPACITY)
44 }
45
46 pub fn append(&mut self, data: &[u8]) {
50 self.total_written += data.len();
51
52 if data.len() >= self.max_size {
54 self.data.clear();
55 let start = data.len() - self.max_size;
56 self.data.extend(&data[start..]);
57 self.bytes_discarded += data.len() - self.max_size;
58 return;
59 }
60
61 let needed_space = (self.data.len() + data.len()).saturating_sub(self.max_size);
63 if needed_space > 0 {
64 self.bytes_discarded += needed_space;
65 for _ in 0..needed_space {
66 self.data.pop_front();
67 }
68 }
69
70 self.data.extend(data);
71 }
72
73 #[must_use]
77 pub fn as_slice(&mut self) -> &[u8] {
78 self.data.make_contiguous()
79 }
80
81 #[must_use]
83 pub fn as_str_lossy(&mut self) -> String {
84 String::from_utf8_lossy(self.as_slice()).into_owned()
85 }
86
87 #[must_use]
89 pub fn len(&self) -> usize {
90 self.data.len()
91 }
92
93 #[must_use]
95 pub fn is_empty(&self) -> bool {
96 self.data.is_empty()
97 }
98
99 #[must_use]
101 pub const fn max_size(&self) -> usize {
102 self.max_size
103 }
104
105 #[must_use]
107 pub const fn total_written(&self) -> usize {
108 self.total_written
109 }
110
111 #[must_use]
113 pub const fn bytes_discarded(&self) -> usize {
114 self.bytes_discarded
115 }
116
117 pub fn clear(&mut self) {
119 self.data.clear();
120 }
121
122 #[must_use]
126 pub fn find(&mut self, needle: &[u8]) -> Option<usize> {
127 if needle.is_empty() {
128 return Some(0);
129 }
130 if needle.len() > self.data.len() {
131 return None;
132 }
133
134 let data = self.as_slice();
135 data.windows(needle.len())
136 .position(|window| window == needle)
137 }
138
139 #[must_use]
141 pub fn find_str(&mut self, needle: &str) -> Option<usize> {
142 self.find(needle.as_bytes())
143 }
144
145 pub fn consume(&mut self, end: usize) -> Vec<u8> {
149 let end = end.min(self.data.len());
150 self.data.drain(..end).collect()
151 }
152
153 pub fn consume_before(&mut self, pos: usize) -> String {
157 let data = self.consume(pos);
158 String::from_utf8_lossy(&data).into_owned()
159 }
160
161 pub fn consume_until(&mut self, needle: &str) -> Option<(String, String)> {
165 let pos = self.find_str(needle)?;
166 let before = self.consume_before(pos);
167 let matched = self.consume(needle.len());
168 Some((before, String::from_utf8_lossy(&matched).into_owned()))
169 }
170
171 #[must_use]
173 pub fn tail(&mut self, n: usize) -> Vec<u8> {
174 let data = self.as_slice();
175 let start = data.len().saturating_sub(n);
176 data[start..].to_vec()
177 }
178
179 #[must_use]
181 pub fn head(&mut self, n: usize) -> Vec<u8> {
182 let data = self.as_slice();
183 let end = n.min(data.len());
184 data[..end].to_vec()
185 }
186
187 #[must_use]
192 pub fn find_in_tail(&mut self, needle: &[u8], window_size: usize) -> Option<usize> {
193 let data = self.as_slice();
194 let search_start = data.len().saturating_sub(window_size);
195 let search_data = &data[search_start..];
196
197 search_data
198 .windows(needle.len())
199 .position(|w| w == needle)
200 .map(|pos| search_start + pos)
201 }
202
203 pub fn search<F, R>(&mut self, f: F) -> R
207 where
208 F: FnOnce(&str) -> R,
209 {
210 let s = self.as_str_lossy();
211 f(&s)
212 }
213}
214
215impl Default for RingBuffer {
216 fn default() -> Self {
217 Self::with_default_capacity()
218 }
219}
220
221impl fmt::Debug for RingBuffer {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 f.debug_struct("RingBuffer")
224 .field("len", &self.len())
225 .field("max_size", &self.max_size)
226 .field("total_written", &self.total_written)
227 .field("bytes_discarded", &self.bytes_discarded)
228 .finish()
229 }
230}
231
232impl std::io::Write for RingBuffer {
233 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
234 self.append(buf);
235 Ok(buf.len())
236 }
237
238 fn flush(&mut self) -> std::io::Result<()> {
239 Ok(())
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn basic_append() {
249 let mut buf = RingBuffer::new(100);
250 buf.append(b"hello");
251 assert_eq!(buf.len(), 5);
252 assert_eq!(buf.as_slice(), b"hello");
253 }
254
255 #[test]
256 fn overflow_discards_oldest() {
257 let mut buf = RingBuffer::new(10);
258 buf.append(b"12345");
259 buf.append(b"67890");
260 buf.append(b"abc");
261
262 assert_eq!(buf.len(), 10);
263 assert_eq!(buf.as_str_lossy(), "4567890abc");
265 }
266
267 #[test]
268 fn find_pattern() {
269 let mut buf = RingBuffer::new(100);
270 buf.append(b"hello world");
271 assert_eq!(buf.find(b"world"), Some(6));
272 assert_eq!(buf.find(b"foo"), None);
273 }
274
275 #[test]
276 fn consume_until() {
277 let mut buf = RingBuffer::new(100);
278 buf.append(b"login: username");
279 let result = buf.consume_until("login:");
280 assert!(result.is_some());
281 let (before, matched) = result.unwrap();
282 assert_eq!(before, "");
283 assert_eq!(matched, "login:");
284 assert_eq!(buf.as_str_lossy(), " username");
285 }
286
287 #[test]
288 fn tail_and_head() {
289 let mut buf = RingBuffer::new(100);
290 buf.append(b"hello world");
291 assert_eq!(buf.tail(5), b"world".to_vec());
292 assert_eq!(buf.head(5), b"hello".to_vec());
293 }
294
295 #[test]
296 fn find_in_tail() {
297 let mut buf = RingBuffer::new(100);
298 buf.append(b"the quick brown fox jumps over the lazy dog");
299 assert!(buf.find_in_tail(b"lazy", 20).is_some());
301 assert!(buf.find_in_tail(b"quick", 20).is_none());
303 }
304
305 #[test]
306 fn write_trait() {
307 use std::io::Write;
308
309 let mut buf = RingBuffer::new(100);
310 write!(buf, "hello world").unwrap();
311 assert_eq!(buf.as_str_lossy(), "hello world");
312 }
313}