From 099f3cde69fe01ee47b7609533331e1208cfcb37 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Tue, 2 Sep 2025 11:12:50 -0300 Subject: [PATCH] refactor: Update filter API to use Read/Write traits Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) --- src/filter_plugin/grep.rs | 53 +++++-------------------------- src/filter_plugin/mod.rs | 53 ++++++++++++++++--------------- src/filter_plugin/strip_ansi.rs | 16 +++++----- src/services/filter_service.rs | 55 +++++++++++++-------------------- 4 files changed, 63 insertions(+), 114 deletions(-) diff --git a/src/filter_plugin/grep.rs b/src/filter_plugin/grep.rs index fc24c3e..806b063 100644 --- a/src/filter_plugin/grep.rs +++ b/src/filter_plugin/grep.rs @@ -1,10 +1,9 @@ use super::FilterPlugin; -use std::io::Result; +use std::io::{Result, Read, Write, BufRead}; use regex::Regex; pub struct GrepFilter { regex: Regex, - buffer: Vec, } impl GrepFilter { @@ -13,55 +12,19 @@ impl GrepFilter { .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; Ok(Self { regex, - buffer: Vec::new(), }) } } impl FilterPlugin for GrepFilter { - fn process(&mut self, data: &[u8]) -> Result> { - self.buffer.extend_from_slice(data); - - let mut result = Vec::new(); - let mut lines = Vec::new(); - let mut start = 0; - - // Split into lines - for (i, &byte) in self.buffer.iter().enumerate() { - if byte == b'\n' { - let line = &self.buffer[start..=i]; - lines.push(line.to_vec()); - start = i + 1; + fn filter(&mut self, reader: &mut R, writer: &mut W) -> Result<()> { + let buf_reader = std::io::BufReader::new(reader); + for line in buf_reader.lines() { + let line = line?; + if self.regex.is_match(&line) { + writeln!(writer, "{}", line)?; } } - - // Keep the remaining data in buffer - let remaining = self.buffer.split_off(start); - self.buffer = remaining; - - // Filter lines that match the regex - for line in lines { - if let Ok(line_str) = std::str::from_utf8(&line) { - if self.regex.is_match(line_str) { - result.extend_from_slice(&line); - } - } - } - - Ok(result) - } - - fn finish(&mut self) -> Result> { - // Process any remaining data in buffer - let mut result = Vec::new(); - if !self.buffer.is_empty() { - if let Ok(line_str) = std::str::from_utf8(&self.buffer) { - if self.regex.is_match(line_str) { - result.extend_from_slice(&self.buffer); - } - } - self.buffer.clear(); - } - Ok(result) + Ok(()) } } diff --git a/src/filter_plugin/mod.rs b/src/filter_plugin/mod.rs index f8843f3..83ef0a1 100644 --- a/src/filter_plugin/mod.rs +++ b/src/filter_plugin/mod.rs @@ -1,4 +1,5 @@ -use std::io::Result; +use std::io::{Result, Read, Write}; +use std::io::BufRead; pub mod head; pub mod tail; @@ -8,8 +9,7 @@ pub mod strip_ansi; pub mod utils; pub trait FilterPlugin: Send { - fn process(&mut self, data: &[u8]) -> Result>; - fn finish(&mut self) -> Result>; + fn filter(&mut self, reader: &mut R, writer: &mut W) -> Result<()>; } pub struct FilterChain { @@ -27,33 +27,32 @@ impl FilterChain { self.plugins.push(plugin); } - pub fn process(&mut self, data: &[u8]) -> Result> { - let mut current_data = data.to_vec(); - for plugin in &mut self.plugins { - // Process the current data through the plugin - let processed = plugin.process(¤t_data)?; - current_data = processed; + pub fn filter(&mut self, reader: &mut R, writer: &mut W) -> Result<()> { + if self.plugins.is_empty() { + // If no plugins, just copy the input to output + std::io::copy(reader, writer)?; + return Ok(()); + } + + // For multiple plugins, we need to chain them together + // We'll use a temporary buffer to hold intermediate results + let mut current_data = Vec::new(); + std::io::copy(reader, &mut current_data)?; + + for (i, plugin) in self.plugins.iter_mut().enumerate() { + let mut input = std::io::Cursor::new(¤t_data); - // Early exit if no data remains - if current_data.is_empty() { - break; + // For the last plugin, write directly to the output writer + if i == self.plugins.len() - 1 { + plugin.filter(&mut input, writer)?; + } else { + // For intermediate plugins, write to a buffer + let mut output = Vec::new(); + plugin.filter(&mut input, &mut output)?; + current_data = output; } } - Ok(current_data) - } - - pub fn finish(&mut self) -> Result> { - let mut result = Vec::new(); - - // Process each plugin's finish method and collect results - for plugin in &mut self.plugins { - let finished_data = plugin.finish()?; - if !finished_data.is_empty() { - result.extend(finished_data); - } - } - - Ok(result) + Ok(()) } } diff --git a/src/filter_plugin/strip_ansi.rs b/src/filter_plugin/strip_ansi.rs index ed3db6a..3acb52a 100644 --- a/src/filter_plugin/strip_ansi.rs +++ b/src/filter_plugin/strip_ansi.rs @@ -1,4 +1,4 @@ -use std::io::Result; +use std::io::{Result, Read, Write}; use strip_ansi_escapes::strip as strip_ansi_escapes; use super::FilterPlugin; @@ -12,13 +12,11 @@ impl StripAnsiFilter { } impl FilterPlugin for StripAnsiFilter { - fn process(&mut self, data: &[u8]) -> Result> { - // Strip ANSI escape sequences from the input data - let stripped = strip_ansi_escapes(data); - Ok(stripped) - } - - fn finish(&mut self) -> Result> { - Ok(Vec::new()) + fn filter(&mut self, reader: &mut R, writer: &mut W) -> Result<()> { + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + let stripped = strip_ansi_escapes(&data); + writer.write_all(&stripped)?; + Ok(()) } } diff --git a/src/services/filter_service.rs b/src/services/filter_service.rs index 089d911..8eccabc 100644 --- a/src/services/filter_service.rs +++ b/src/services/filter_service.rs @@ -1,5 +1,5 @@ use crate::filter_plugin::{FilterChain, parse_filter_string}; -use std::io::Result; +use std::io::{Result, Read, Write}; pub struct FilterService; @@ -16,44 +16,33 @@ impl FilterService { } } - pub fn process_data(&self, chain: &mut Option, data: &[u8]) -> Result> { + pub fn filter_data( + &self, + chain: &mut Option, + reader: &mut R, + writer: &mut W + ) -> Result<()> { if let Some(chain) = chain { - chain.process(data) + chain.filter(reader, writer) } else { - Ok(data.to_vec()) + // If no filter chain, just copy the input to output + std::io::copy(reader, writer)?; + Ok(()) } } - pub fn finish_processing(&self, chain: &mut Option) -> Result> { - if let Some(chain) = chain { - chain.finish() - } else { - Ok(Vec::new()) - } - } - - // Add a method to process data through the filter chain and handle finish automatically - pub fn process_all_data(&self, chain: &mut Option, data: &[u8]) -> Result> { - let mut processed = if let Some(chain) = chain { - chain.process(data)? - } else { - data.to_vec() - }; - - // If we have a chain, also get any remaining data from finish() - if chain.is_some() { - let finished = self.finish_processing(chain)?; - if !finished.is_empty() { - processed.extend(finished); - } - } - - Ok(processed) - } - - // Helper method to create and process data with a filter string in one call + // Helper method to process data with a filter string in one call pub fn process_with_filter(&self, data: &[u8], filter_str: Option<&str>) -> Result> { let mut chain = self.create_filter_chain(filter_str)?; - self.process_all_data(&mut chain, data) + let mut reader = std::io::Cursor::new(data); + let mut writer = Vec::new(); + + if let Some(ref mut chain) = chain { + chain.filter(&mut reader, &mut writer)?; + } else { + std::io::copy(&mut reader, &mut writer)?; + } + + Ok(writer) } }