From edfe0fcd6e0ac6e4a1cb8548514a1018412c33e0 Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Wed, 10 Sep 2025 10:46:39 -0300 Subject: [PATCH] docs: Add comprehensive rustdoc to filter plugin modules Co-authored-by: aider (openai/andrew/openrouter/sonoma-sky-alpha) --- src/filter_plugin/exec.rs | 16 +++++++ src/filter_plugin/grep.rs | 18 ++++++++ src/filter_plugin/head.rs | 28 +++++++++++++ src/filter_plugin/mod.rs | 74 ++++++++++++++++++++++++++++++--- src/filter_plugin/skip.rs | 30 +++++++++++++ src/filter_plugin/strip_ansi.rs | 10 +++++ src/filter_plugin/tail.rs | 30 +++++++++++++ src/filter_plugin/utils.rs | 20 ++++++++- 8 files changed, 219 insertions(+), 7 deletions(-) diff --git a/src/filter_plugin/exec.rs b/src/filter_plugin/exec.rs index b1f5549..7a2cb6c 100644 --- a/src/filter_plugin/exec.rs +++ b/src/filter_plugin/exec.rs @@ -4,6 +4,7 @@ use std::process::{Command, Stdio, Child}; use which::which; use log::*; +/// A filter that executes an external program and pipes input through it. #[derive(Debug, Clone)] pub struct ExecFilter { program: String, @@ -16,6 +17,13 @@ pub struct ExecFilter { } impl ExecFilter { + /// Creates a new `ExecFilter` for the specified program and arguments. + /// + /// # Arguments + /// + /// * `program` - The name or path of the program to execute. + /// * `args` - The arguments to pass to the program. + /// * `split_whitespace` - Whether to split arguments on whitespace. pub fn new( program: &str, args: Vec<&str>, @@ -37,6 +45,12 @@ impl ExecFilter { } impl FilterPlugin for ExecFilter { + /// Filters the input by piping it through the external program and writing the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { if !self.supported { return Err(std::io::Error::new( @@ -146,6 +160,7 @@ impl FilterPlugin for ExecFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(ExecFilter { program: self.program.clone(), @@ -158,6 +173,7 @@ impl FilterPlugin for ExecFilter { }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { diff --git a/src/filter_plugin/grep.rs b/src/filter_plugin/grep.rs index cedad4c..9f53160 100644 --- a/src/filter_plugin/grep.rs +++ b/src/filter_plugin/grep.rs @@ -2,11 +2,21 @@ use super::{FilterPlugin, FilterOption}; use std::io::{Result, Read, Write, BufRead}; use regex::Regex; +/// A filter that matches lines against a regular expression pattern. pub struct GrepFilter { regex: Regex, } impl GrepFilter { + /// Creates a new `GrepFilter` with the specified regex pattern. + /// + /// # Arguments + /// + /// * `pattern` - The regular expression pattern to match. + /// + /// # Errors + /// + /// Returns an error if the pattern is invalid. pub fn new(pattern: String) -> Result { let regex = Regex::new(&pattern) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; @@ -17,6 +27,12 @@ impl GrepFilter { } impl FilterPlugin for GrepFilter { + /// Filters the input by writing only lines that match the regex pattern. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { let mut buf_reader = std::io::BufReader::new(&mut *reader); for line in buf_reader.by_ref().lines() { @@ -28,12 +44,14 @@ impl FilterPlugin for GrepFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { regex: self.regex.clone(), }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { diff --git a/src/filter_plugin/head.rs b/src/filter_plugin/head.rs index 509899e..1f5d9d1 100644 --- a/src/filter_plugin/head.rs +++ b/src/filter_plugin/head.rs @@ -3,11 +3,17 @@ use std::io::{Result, Read, Write, BufRead}; use crate::common::PIPESIZE; use crate::services::filter_service::register_filter_plugin; +/// A filter that reads the first N bytes from the input stream. pub struct HeadBytesFilter { remaining: usize, } impl HeadBytesFilter { + /// Creates a new `HeadBytesFilter` that will read up to the specified number of bytes. + /// + /// # Arguments + /// + /// * `count` - The maximum number of bytes to read. pub fn new(count: usize) -> Self { Self { remaining: count, @@ -16,6 +22,12 @@ impl HeadBytesFilter { } impl FilterPlugin for HeadBytesFilter { + /// Filters the input by reading only the first N bytes and writing them to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { if self.remaining == 0 { return Ok(()); @@ -34,12 +46,14 @@ impl FilterPlugin for HeadBytesFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { remaining: self.remaining, }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { @@ -51,11 +65,17 @@ impl FilterPlugin for HeadBytesFilter { } } +/// A filter that reads the first N lines from the input stream. pub struct HeadLinesFilter { remaining: usize, } impl HeadLinesFilter { + /// Creates a new `HeadLinesFilter` that will read up to the specified number of lines. + /// + /// # Arguments + /// + /// * `count` - The maximum number of lines to read. pub fn new(count: usize) -> Self { Self { remaining: count, @@ -64,6 +84,12 @@ impl HeadLinesFilter { } impl FilterPlugin for HeadLinesFilter { + /// Filters the input by reading only the first N lines and writing them to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { if self.remaining == 0 { return Ok(()); @@ -81,12 +107,14 @@ impl FilterPlugin for HeadLinesFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { remaining: self.remaining, }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { diff --git a/src/filter_plugin/mod.rs b/src/filter_plugin/mod.rs index 17fd8b4..b931f74 100644 --- a/src/filter_plugin/mod.rs +++ b/src/filter_plugin/mod.rs @@ -17,6 +17,7 @@ pub use skip::{SkipBytesFilter, SkipLinesFilter}; pub use grep::GrepFilter; pub use strip_ansi::StripAnsiFilter; +/// Represents an option for a filter plugin. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)] pub struct FilterOption { pub name: String, @@ -25,13 +26,26 @@ pub struct FilterOption { pub required: bool, } +/// Trait for filter plugins that process input streams. pub trait FilterPlugin: Send { + /// Processes the input stream and writes the filtered output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. + /// + /// # Returns + /// + /// A `Result` indicating success or failure. fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()>; + /// Clones this plugin into a new boxed instance. fn clone_box(&self) -> Box; - // Get the filter options definition + /// Returns the configuration options for this plugin. fn options(&self) -> Vec; } +/// Enum representing the different types of filters. #[derive(Debug, EnumString, strum::VariantNames, strum::Display)] #[strum(serialize_all = "snake_case")] pub enum FilterType { @@ -45,11 +59,13 @@ pub enum FilterType { StripAnsi, } +/// A chain of filter plugins applied sequentially. pub struct FilterChain { plugins: Vec>, } impl Clone for FilterChain { + /// Clones this filter chain. fn clone(&self) -> Self { let mut plugins = Vec::with_capacity(self.plugins.len()); for plugin in &self.plugins { @@ -60,22 +76,35 @@ impl Clone for FilterChain { } impl Clone for Box { + /// Clones the boxed filter plugin. fn clone(&self) -> Self { self.clone_box() } } impl FilterChain { + /// Creates a new empty filter chain. pub fn new() -> Self { Self { plugins: Vec::new(), } } + /// Adds a plugin to the chain. pub fn add_plugin(&mut self, plugin: Box) { self.plugins.push(plugin); } + /// Applies the filter chain to the input and writes to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. + /// + /// # Returns + /// + /// A `Result` indicating success or failure. pub fn filter(&mut self, reader: &mut dyn Read, writer: &mut dyn Write) -> Result<()> { if self.plugins.is_empty() { // If no plugins, just copy the input to output @@ -112,7 +141,15 @@ impl FilterChain { } } -// Helper function to parse filter string and create appropriate plugins +/// Parses a filter string into a `FilterChain`. +/// +/// # Arguments +/// +/// * `filter_str` - The filter string (e.g., "head_lines(10)|grep(pattern=error)"). +/// +/// # Returns +/// +/// A `Result` containing the parsed `FilterChain` or an error if invalid. pub fn parse_filter_string(filter_str: &str) -> Result { let mut chain = FilterChain::new(); @@ -183,7 +220,17 @@ pub fn parse_filter_string(filter_str: &str) -> Result { Ok(chain) } -// Helper function to create filter with proper option handling +/// Creates a filter plugin with the given options. +/// +/// # Arguments +/// +/// * `filter_type` - The type of filter to create. +/// * `unnamed_params` - Unnamed parameters. +/// * `named_options` - Named options. +/// +/// # Returns +/// +/// A `Result` containing the boxed filter plugin. fn create_filter_with_options( filter_type: FilterType, unnamed_params: &[serde_json::Value], @@ -250,7 +297,16 @@ fn create_filter_with_options( create_specific_filter(filter_type, &options) } -// Helper to create specific filter instances based on options +/// Creates a specific filter instance based on type and options. +/// +/// # Arguments +/// +/// * `filter_type` - The type of filter. +/// * `options` - The processed options. +/// +/// # Returns +/// +/// A `Result` containing the boxed filter plugin. fn create_specific_filter( filter_type: FilterType, options: &HashMap, @@ -338,7 +394,15 @@ fn create_specific_filter( } } -// Helper function to parse option values +/// Parses an option value from a string into a JSON value. +/// +/// # Arguments +/// +/// * `input` - The input string. +/// +/// # Returns +/// +/// A `Result` containing the parsed JSON value. fn parse_option_value(input: &str) -> Result { // Remove quotes if present let input = input.trim_matches(|c| c == '\'' || c == '"'); diff --git a/src/filter_plugin/skip.rs b/src/filter_plugin/skip.rs index c34bee9..d673fbe 100644 --- a/src/filter_plugin/skip.rs +++ b/src/filter_plugin/skip.rs @@ -3,17 +3,24 @@ use std::io::{Result, Read, Write, BufRead}; use crate::common::PIPESIZE; use crate::services::filter_service::register_filter_plugin; +/// A filter that skips the first N bytes from the input stream. pub struct SkipBytesFilter { remaining: usize, } impl SkipBytesFilter { + /// Creates a new `SkipBytesFilter` that will skip the specified number of bytes. + /// + /// # Arguments + /// + /// * `count` - The number of bytes to skip. pub fn new(count: usize) -> Self { Self { remaining: count, } } + /// Creates a new instance from options. pub fn create(options: Option) -> Result> { let options = options.ok_or_else(|| { std::io::Error::new( @@ -37,6 +44,12 @@ impl SkipBytesFilter { } impl FilterPlugin for SkipBytesFilter { + /// Filters the input by skipping the first N bytes and writing the rest to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { // Skip bytes in chunks if self.remaining > 0 { @@ -56,12 +69,14 @@ impl FilterPlugin for SkipBytesFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { remaining: self.remaining, }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { @@ -73,17 +88,24 @@ impl FilterPlugin for SkipBytesFilter { } } +/// A filter that skips the first N lines from the input stream. pub struct SkipLinesFilter { remaining: usize, } impl SkipLinesFilter { + /// Creates a new `SkipLinesFilter` that will skip the specified number of lines. + /// + /// # Arguments + /// + /// * `count` - The number of lines to skip. pub fn new(count: usize) -> Self { Self { remaining: count, } } + /// Creates a new instance from options. pub fn create(options: Option) -> Result> { let options = options.ok_or_else(|| { std::io::Error::new( @@ -107,6 +129,12 @@ impl SkipLinesFilter { } impl FilterPlugin for SkipLinesFilter { + /// Filters the input by skipping the first N lines and writing the rest to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { let mut buf_reader = std::io::BufReader::new(&mut *reader); for line in buf_reader.by_ref().lines() { @@ -120,12 +148,14 @@ impl FilterPlugin for SkipLinesFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { remaining: self.remaining, }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { diff --git a/src/filter_plugin/strip_ansi.rs b/src/filter_plugin/strip_ansi.rs index 9a043bb..c6d9cbe 100644 --- a/src/filter_plugin/strip_ansi.rs +++ b/src/filter_plugin/strip_ansi.rs @@ -2,25 +2,35 @@ use std::io::{Result, Read, Write}; use strip_ansi_escapes::Writer; use super::{FilterPlugin, FilterOption}; +/// A filter that removes ANSI escape sequences from the input. pub struct StripAnsiFilter; impl StripAnsiFilter { + /// Creates a new `StripAnsiFilter`. pub fn new() -> Self { Self } } impl FilterPlugin for StripAnsiFilter { + /// Filters the input by stripping ANSI escape sequences and writing the plain text to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { let mut ansi_writer = Writer::new(&mut *writer); std::io::copy(&mut *reader, &mut ansi_writer)?; ansi_writer.flush() } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self) } + /// Returns the configuration options for this filter (none required). fn options(&self) -> Vec { Vec::new() // strip_ansi doesn't take any options } diff --git a/src/filter_plugin/tail.rs b/src/filter_plugin/tail.rs index 06285c9..4b664c6 100644 --- a/src/filter_plugin/tail.rs +++ b/src/filter_plugin/tail.rs @@ -4,12 +4,18 @@ use std::collections::VecDeque; use crate::common::PIPESIZE; use crate::services::filter_service::register_filter_plugin; +/// A filter that reads the last N bytes from the input stream. pub struct TailBytesFilter { buffer: VecDeque, count: usize, } impl TailBytesFilter { + /// Creates a new `TailBytesFilter` that will keep the last specified number of bytes. + /// + /// # Arguments + /// + /// * `count` - The number of bytes to keep from the end. pub fn new(count: usize) -> Self { Self { buffer: VecDeque::with_capacity(count), @@ -17,6 +23,7 @@ impl TailBytesFilter { } } + /// Creates a new instance from options. pub fn create(options: Option) -> Result> { let options = options.ok_or_else(|| { std::io::Error::new( @@ -40,6 +47,12 @@ impl TailBytesFilter { } impl FilterPlugin for TailBytesFilter { + /// Filters the input by keeping only the last N bytes and writing them to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { let mut temp_buffer = vec![0; PIPESIZE]; loop { @@ -63,6 +76,7 @@ impl FilterPlugin for TailBytesFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { buffer: self.buffer.clone(), @@ -70,6 +84,7 @@ impl FilterPlugin for TailBytesFilter { }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { @@ -81,12 +96,18 @@ impl FilterPlugin for TailBytesFilter { } } +/// A filter that reads the last N lines from the input stream. pub struct TailLinesFilter { lines: VecDeque, count: usize, } impl TailLinesFilter { + /// Creates a new `TailLinesFilter` that will keep the last specified number of lines. + /// + /// # Arguments + /// + /// * `count` - The number of lines to keep from the end. pub fn new(count: usize) -> Self { Self { lines: VecDeque::with_capacity(count), @@ -94,6 +115,7 @@ impl TailLinesFilter { } } + /// Creates a new instance from options. pub fn create(options: Option) -> Result> { let options = options.ok_or_else(|| { std::io::Error::new( @@ -117,6 +139,12 @@ impl TailLinesFilter { } impl FilterPlugin for TailLinesFilter { + /// Filters the input by keeping only the last N lines and writing them to the output. + /// + /// # Arguments + /// + /// * `reader` - The input reader. + /// * `writer` - The output writer. fn filter(&mut self, mut reader: Box<&mut dyn Read>, mut writer: Box<&mut dyn Write>) -> Result<()> { let mut buf_reader = std::io::BufReader::new(&mut *reader); for line in buf_reader.by_ref().lines() { @@ -134,6 +162,7 @@ impl FilterPlugin for TailLinesFilter { Ok(()) } + /// Clones this filter into a new boxed instance. fn clone_box(&self) -> Box { Box::new(Self { lines: self.lines.clone(), @@ -141,6 +170,7 @@ impl FilterPlugin for TailLinesFilter { }) } + /// Returns the configuration options for this filter. fn options(&self) -> Vec { vec![ FilterOption { diff --git a/src/filter_plugin/utils.rs b/src/filter_plugin/utils.rs index 15d80af..d636d7f 100644 --- a/src/filter_plugin/utils.rs +++ b/src/filter_plugin/utils.rs @@ -1,11 +1,27 @@ use std::io::Result; -/// Helper function to create a filter chain from a string +/// Creates a filter chain from a filter string specification. +/// +/// # Arguments +/// +/// * `filter_str` - The string describing the filter chain (e.g., "head_lines(10)|grep(pattern=error)"). +/// +/// # Returns +/// +/// A `Result` containing an optional `FilterChain` if parsing succeeds. pub fn create_filter_chain(filter_str: &str) -> Result> { super::parse_filter_string(filter_str).map(Some) } -/// Helper function to parse a number from a string with error handling +/// Parses a string into a number of type T. +/// +/// # Arguments +/// +/// * `s` - The string to parse. +/// +/// # Returns +/// +/// A `Result` containing the parsed number or an error if invalid. pub fn parse_number(s: &str) -> Result { s.parse::() .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid number"))