use crate::config::OutputConfig;
use crate::search::SearchResult;
use anyhow::Result;
use std::io::Write;
pub mod csv;
pub mod html;
pub mod json;
pub mod text;
pub mod xml;
pub trait OutputFormatter {
fn format<W: Write>(
&self,
results: &[SearchResult],
writer: &mut W,
config: &OutputConfig,
) -> Result<()>;
}
pub enum Formatter {
Text(text::TextFormatter),
Json(json::JsonFormatter),
Html(html::HtmlFormatter),
Xml(xml::XmlFormatter),
Csv(csv::CsvFormatter),
}
impl Formatter {
pub fn format<W: Write>(
&self,
results: &[SearchResult],
writer: &mut W,
config: &OutputConfig,
) -> Result<()> {
match self {
Formatter::Text(f) => f.format(results, writer, config),
Formatter::Json(f) => f.format(results, writer, config),
Formatter::Html(f) => f.format(results, writer, config),
Formatter::Xml(f) => f.format(results, writer, config),
Formatter::Csv(f) => f.format(results, writer, config),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
JsonPretty,
Html,
Xml,
Csv,
}
impl std::str::FromStr for OutputFormat {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"text" | "txt" => Ok(OutputFormat::Text),
"json" => Ok(OutputFormat::Json),
"json-pretty" | "pretty-json" => Ok(OutputFormat::JsonPretty),
"html" => Ok(OutputFormat::Html),
"xml" => Ok(OutputFormat::Xml),
"csv" => Ok(OutputFormat::Csv),
_ => Err(anyhow::anyhow!("Unknown output format: {}", s)),
}
}
}
pub fn create_formatter(format: OutputFormat) -> Formatter {
match format {
OutputFormat::Text => Formatter::Text(text::TextFormatter::new()),
OutputFormat::Json => Formatter::Json(json::JsonFormatter::new(false)),
OutputFormat::JsonPretty => Formatter::Json(json::JsonFormatter::new(true)),
OutputFormat::Html => Formatter::Html(html::HtmlFormatter::new()),
OutputFormat::Xml => Formatter::Xml(xml::XmlFormatter::new()),
OutputFormat::Csv => Formatter::Csv(csv::CsvFormatter::new()),
}
}
#[derive(Debug, Clone, Default)]
pub struct OutputStats {
pub files_processed: usize,
pub total_matches: usize,
pub files_with_matches: usize,
pub processing_time_ms: u64,
}
impl OutputStats {
pub fn new() -> Self {
Self::default()
}
pub fn update_with_results(&mut self, results: &[SearchResult]) {
self.files_processed = results.len();
self.total_matches = results.iter().map(|r| r.matches.len()).sum();
self.files_with_matches = results.iter().filter(|r| !r.matches.is_empty()).count();
}
}