use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct CircularBuffer {
buffer: VecDeque<String>,
max_size: usize,
total_lines_added: u64, }
impl CircularBuffer {
pub fn new(max_size: usize) -> Self {
Self {
buffer: VecDeque::with_capacity(max_size),
max_size,
total_lines_added: 0,
}
}
pub fn push(&mut self, line: String) {
if self.buffer.len() >= self.max_size && self.max_size > 0 {
self.buffer.pop_front(); }
self.buffer.push_back(line);
self.total_lines_added += 1;
}
pub fn len(&self) -> usize {
self.buffer.len()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn get_all_lines(&self) -> Vec<String> {
self.buffer.iter().cloned().collect()
}
pub fn get_last_lines(&self, n: usize) -> Vec<String> {
let start_idx = if n >= self.buffer.len() {
0
} else {
self.buffer.len() - n
};
self.buffer.iter().skip(start_idx).cloned().collect()
}
pub fn get_lines_range(&self, start: usize, count: usize) -> Vec<String> {
if start >= self.buffer.len() {
return Vec::new();
}
let end = (start + count).min(self.buffer.len());
self.buffer.range(start..end).cloned().collect()
}
pub fn get_content(&self) -> String {
self.buffer.iter().cloned().collect::<Vec<_>>().join("\n")
}
pub fn get_recent_content(&self, lines: usize) -> String {
self.get_last_lines(lines).join("\n")
}
pub fn clear(&mut self) {
self.buffer.clear();
}
pub fn max_size(&self) -> usize {
self.max_size
}
pub fn set_max_size(&mut self, new_max_size: usize) {
self.max_size = new_max_size;
while self.buffer.len() > new_max_size && new_max_size > 0 {
self.buffer.pop_front();
}
if new_max_size > 0 {
self.buffer.reserve(new_max_size);
}
}
pub fn get_stats(&self) -> BufferStats {
BufferStats {
current_lines: self.buffer.len(),
max_size: self.max_size,
total_lines_added: self.total_lines_added,
memory_usage_bytes: self.estimate_memory_usage(),
is_at_capacity: self.buffer.len() >= self.max_size,
}
}
fn estimate_memory_usage(&self) -> usize {
let string_overhead = std::mem::size_of::<String>();
let content_size: usize = self.buffer.iter().map(|s| s.len()).sum();
let deque_overhead =
std::mem::size_of::<VecDeque<String>>() + (self.buffer.capacity() * string_overhead);
content_size + deque_overhead
}
pub fn search(&self, query: &str) -> Vec<(usize, String)> {
let query_lower = query.to_lowercase();
self.buffer
.iter()
.enumerate()
.filter_map(|(idx, line)| {
if line.to_lowercase().contains(&query_lower) {
Some((idx, line.clone()))
} else {
None
}
})
.collect()
}
pub fn get_lines_reverse(&self) -> Vec<String> {
self.buffer.iter().rev().cloned().collect()
}
}
#[derive(Debug, Clone)]
pub struct BufferStats {
pub current_lines: usize,
pub max_size: usize,
pub total_lines_added: u64,
pub memory_usage_bytes: usize,
pub is_at_capacity: bool,
}
impl Default for CircularBuffer {
fn default() -> Self {
Self::new(1000)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circular_buffer_basic_operations() {
let mut buffer = CircularBuffer::new(3);
assert_eq!(buffer.len(), 0);
assert!(buffer.is_empty());
buffer.push("line1".to_string());
buffer.push("line2".to_string());
buffer.push("line3".to_string());
assert_eq!(buffer.len(), 3);
assert!(!buffer.is_empty());
assert_eq!(buffer.get_all_lines(), vec!["line1", "line2", "line3"]);
}
#[test]
fn test_circular_buffer_overflow() {
let mut buffer = CircularBuffer::new(2);
buffer.push("line1".to_string());
buffer.push("line2".to_string());
buffer.push("line3".to_string());
assert_eq!(buffer.len(), 2);
assert_eq!(buffer.get_all_lines(), vec!["line2", "line3"]);
buffer.push("line4".to_string()); assert_eq!(buffer.get_all_lines(), vec!["line3", "line4"]);
}
#[test]
fn test_circular_buffer_get_last_lines() {
let mut buffer = CircularBuffer::new(5);
for i in 1..=5 {
buffer.push(format!("line{}", i));
}
assert_eq!(buffer.get_last_lines(2), vec!["line4", "line5"]);
assert_eq!(
buffer.get_last_lines(10),
vec!["line1", "line2", "line3", "line4", "line5"]
);
}
#[test]
fn test_circular_buffer_search() {
let mut buffer = CircularBuffer::new(5);
buffer.push("error: file not found".to_string());
buffer.push("info: processing data".to_string());
buffer.push("warning: deprecated function".to_string());
buffer.push("error: connection failed".to_string());
let results = buffer.search("error");
assert_eq!(results.len(), 2);
assert_eq!(results[0].0, 0); assert_eq!(results[1].0, 3); }
#[test]
fn test_circular_buffer_resize() {
let mut buffer = CircularBuffer::new(5);
for i in 1..=5 {
buffer.push(format!("line{}", i));
}
buffer.set_max_size(3);
assert_eq!(buffer.len(), 3);
assert_eq!(buffer.get_all_lines(), vec!["line3", "line4", "line5"]);
}
#[test]
fn test_circular_buffer_stats() {
let mut buffer = CircularBuffer::new(2);
buffer.push("test1".to_string());
buffer.push("test2".to_string());
buffer.push("test3".to_string());
let stats = buffer.get_stats();
assert_eq!(stats.current_lines, 2);
assert_eq!(stats.max_size, 2);
assert_eq!(stats.total_lines_added, 3);
assert!(stats.is_at_capacity);
assert!(stats.memory_usage_bytes > 0);
}
#[test]
fn test_circular_buffer_content_methods() {
let mut buffer = CircularBuffer::new(3);
buffer.push("line1".to_string());
buffer.push("line2".to_string());
buffer.push("line3".to_string());
assert_eq!(buffer.get_content(), "line1\nline2\nline3");
assert_eq!(buffer.get_recent_content(2), "line2\nline3");
}
}