praxis_llm/buffer_utils/
buffering.rs1use anyhow::Result;
2use std::collections::VecDeque;
3
4pub struct CircularLineBuffer {
7 buffer: VecDeque<u8>,
8}
9
10impl CircularLineBuffer {
11 pub fn with_capacity(capacity: usize) -> Self {
13 Self {
14 buffer: VecDeque::with_capacity(capacity),
15 }
16 }
17
18 pub fn extend(&mut self, bytes: &[u8]) {
20 self.buffer.extend(bytes);
21 }
22
23 pub fn next_line(&mut self) -> Option<Result<String>> {
26 let newline_pos = self.buffer.iter().position(|&b| b == b'\n')?;
28
29 let line_bytes: Vec<u8> = self.buffer.drain(..=newline_pos).collect();
31
32 match std::str::from_utf8(&line_bytes) {
34 Ok(line_str) => Some(Ok(line_str.trim().to_string())),
35 Err(e) => Some(Err(anyhow::anyhow!("Invalid UTF-8: {}", e))),
36 }
37 }
38
39 pub fn len(&self) -> usize {
41 self.buffer.len()
42 }
43
44 pub fn is_empty(&self) -> bool {
46 self.buffer.is_empty()
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_circular_buffer_basic() {
56 let mut buffer = CircularLineBuffer::with_capacity(64);
57
58 buffer.extend(b"line1\nline2\n");
59
60 assert_eq!(buffer.next_line().unwrap().unwrap(), "line1");
61 assert_eq!(buffer.next_line().unwrap().unwrap(), "line2");
62 assert!(buffer.next_line().is_none());
63 }
64
65 #[test]
66 fn test_partial_line() {
67 let mut buffer = CircularLineBuffer::with_capacity(64);
68
69 buffer.extend(b"partial");
70 assert!(buffer.next_line().is_none());
71
72 buffer.extend(b" line\n");
73 assert_eq!(buffer.next_line().unwrap().unwrap(), "partial line");
74 }
75}
76