use crate::types::SearchResult;
use std::fs::File;
use std::io::Write;
pub fn export_results(
results: &[SearchResult],
path: &str,
query: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let format = if path.ends_with(".csv") {
ExportFormat::Csv
} else if path.ends_with(".md") || path.ends_with(".markdown") {
ExportFormat::Markdown
} else {
ExportFormat::Text
};
match format {
ExportFormat::Csv => export_csv(results, path),
ExportFormat::Markdown => export_markdown(results, path, query),
ExportFormat::Text => export_text(results, path, query),
}
}
enum ExportFormat {
Csv,
Markdown,
Text,
}
fn export_csv(results: &[SearchResult], path: &str) -> Result<(), Box<dyn std::error::Error>> {
let file = File::create(path)?;
let mut wtr = csv::Writer::from_writer(file);
wtr.write_record(["File", "Line", "Content", "Score", "Relevance"])?;
for result in results {
wtr.write_record([
&result.file,
&result.line_number.to_string(),
&result.content,
&format!("{:.2}", result.score),
&result.relevance,
])?;
}
wtr.flush()?;
Ok(())
}
fn export_markdown(
results: &[SearchResult],
path: &str,
query: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut file = File::create(path)?;
writeln!(file, "# Search Results")?;
writeln!(file)?;
writeln!(file, "**Query:** `{}`", query)?;
writeln!(file, "**Total Results:** {}", results.len())?;
writeln!(file)?;
let mut current_file = "";
for result in results {
if result.file != current_file {
writeln!(file)?;
writeln!(file, "## {}", result.file)?;
writeln!(file)?;
current_file = &result.file;
}
writeln!(
file,
"- **Line {}** (Score: {:.0}): `{}`",
result.line_number,
result.score,
result.content.trim()
)?;
}
writeln!(file)?;
writeln!(file, "---")?;
writeln!(file, "*Generated by codesearch*")?;
Ok(())
}
fn export_text(
results: &[SearchResult],
path: &str,
query: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut file = File::create(path)?;
writeln!(file, "Search Results for: {}", query)?;
writeln!(file, "Total: {} results", results.len())?;
writeln!(file, "{}", "=".repeat(60))?;
writeln!(file)?;
for result in results {
writeln!(
file,
"{}:{} {}",
result.file,
result.line_number,
result.content.trim()
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Match;
use tempfile::tempdir;
fn create_test_results() -> Vec<SearchResult> {
vec![SearchResult {
file: "test.rs".to_string(),
line_number: 10,
content: "fn main() {}".to_string(),
matches: vec![Match {
start: 0,
end: 2,
text: "fn".to_string(),
}],
score: 85.0,
relevance: "High".to_string(),
}]
}
#[test]
fn test_export_csv() {
let dir = tempdir().unwrap();
let path = dir.path().join("results.csv");
let results = create_test_results();
export_csv(&results, path.to_str().unwrap()).unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("File"));
assert!(content.contains("test.rs"));
}
#[test]
fn test_export_markdown() {
let dir = tempdir().unwrap();
let path = dir.path().join("results.md");
let results = create_test_results();
export_markdown(&results, path.to_str().unwrap(), "fn").unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("# Search Results"));
assert!(content.contains("test.rs"));
}
#[test]
fn test_export_text() {
let dir = tempdir().unwrap();
let path = dir.path().join("results.txt");
let results = create_test_results();
export_text(&results, path.to_str().unwrap(), "fn").unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("Search Results"));
assert!(content.contains("test.rs"));
}
}