refactor: Update filter API to use Read/Write traits

Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-09-02 11:12:50 -03:00
parent fc413738b7
commit 099f3cde69
4 changed files with 63 additions and 114 deletions

View File

@@ -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<u8>,
}
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<Vec<u8>> {
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<R: Read, W: Write>(&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<Vec<u8>> {
// 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(())
}
}

View File

@@ -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<Vec<u8>>;
fn finish(&mut self) -> Result<Vec<u8>>;
fn filter<R: Read, W: Write>(&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<Vec<u8>> {
let mut current_data = data.to_vec();
for plugin in &mut self.plugins {
// Process the current data through the plugin
let processed = plugin.process(&current_data)?;
current_data = processed;
pub fn filter<R: Read, W: Write>(&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(&current_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<Vec<u8>> {
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(())
}
}

View File

@@ -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<Vec<u8>> {
// Strip ANSI escape sequences from the input data
let stripped = strip_ansi_escapes(data);
Ok(stripped)
}
fn finish(&mut self) -> Result<Vec<u8>> {
Ok(Vec::new())
fn filter<R: Read, W: Write>(&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(())
}
}

View File

@@ -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<FilterChain>, data: &[u8]) -> Result<Vec<u8>> {
pub fn filter_data<R: Read, W: Write>(
&self,
chain: &mut Option<FilterChain>,
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<FilterChain>) -> Result<Vec<u8>> {
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<FilterChain>, data: &[u8]) -> Result<Vec<u8>> {
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<Vec<u8>> {
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)
}
}