use crate::constants;
use crate::core_types::FileInfo;
use crate::output::formatter::format_path_for_display;
use crate::output::OutputConfig;
use anyhow::Result;
use log::debug;
use std::io::Write;
pub(crate) fn write_summary(
writer: &mut dyn Write,
files: &[&FileInfo], opts: &OutputConfig,
) -> Result<()> {
debug!("Writing summary for {} files...", files.len());
writeln!(writer, "{}", constants::SUMMARY_SEPARATOR)?;
writeln!(
writer,
"{}: ({})",
constants::SUMMARY_HEADER_PREFIX,
files.len()
)?;
let mut sorted_files = files.to_vec();
sorted_files.sort_by_key(|fi| &fi.relative_path);
for file_info in sorted_files {
let path_str = format_path_for_display(&file_info.relative_path, opts);
if opts.counts {
if let Some(counts) = file_info.counts {
if file_info.is_binary {
writeln!(writer, "- {} (Binary C:{})", path_str, counts.characters)?;
} else {
writeln!(
writer,
"- {} (L:{} C:{} W:{})",
path_str, counts.lines, counts.characters, counts.words
)?;
}
} else {
log::warn!("Counts requested but not available for: {}", path_str);
writeln!(writer, "- {} (Counts not available)", path_str)?;
}
} else {
writeln!(writer, "- {}", path_str)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core_types::{FileCounts, FileInfo};
use std::io::Cursor;
use std::path::PathBuf;
fn create_test_opts(counts: bool, backticks: bool) -> OutputConfig {
let mut opts =
crate::output::tests::create_mock_output_config(backticks, false, false, true);
opts.counts = counts;
opts.summary = true; opts
}
fn create_file_info(
relative_path: &str,
counts: Option<FileCounts>,
is_binary: bool,
) -> FileInfo {
FileInfo {
absolute_path: PathBuf::from("/absolute/path/to").join(relative_path),
relative_path: PathBuf::from(relative_path),
size: 100,
processed_content: Some("dummy content".to_string()),
counts,
is_binary,
..Default::default()
}
}
#[test]
fn test_summary_empty() -> Result<()> {
let opts = create_test_opts(false, false);
let files = vec![];
let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (0)\n";
assert_eq!(output, expected);
Ok(())
}
#[test]
fn test_summary_no_counts() -> Result<()> {
let opts = create_test_opts(false, false);
let fi1 = create_file_info("z_file.txt", None, false);
let fi2 = create_file_info("a_file.rs", None, false);
let fi3 = create_file_info("sub/b_file.md", None, false);
let files = vec![&fi1, &fi2, &fi3]; let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (3)\n- a_file.rs\n- sub/b_file.md\n- z_file.txt\n";
assert_eq!(output, expected);
Ok(())
}
#[test]
fn test_summary_with_counts() -> Result<()> {
let opts = create_test_opts(true, false); let counts1 = Some(FileCounts {
lines: 10,
characters: 100,
words: 20,
});
let counts2 = Some(FileCounts {
lines: 1,
characters: 5,
words: 1,
});
let fi1 = create_file_info("z_file.txt", counts1, false);
let fi2 = create_file_info("a_file.rs", counts2, false);
let files = vec![&fi1, &fi2];
let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (2)\n- a_file.rs (L:1 C:5 W:1)\n- z_file.txt (L:10 C:100 W:20)\n";
assert_eq!(output, expected);
Ok(())
}
#[test]
fn test_summary_with_counts_missing() -> Result<()> {
let opts = create_test_opts(true, false); let counts1 = Some(FileCounts {
lines: 10,
characters: 100,
words: 20,
});
let fi1 = create_file_info("z_file.txt", counts1, false);
let fi2 = create_file_info("a_file.rs", None, false); let files = vec![&fi1, &fi2];
let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (2)\n- a_file.rs (Counts not available)\n- z_file.txt (L:10 C:100 W:20)\n";
assert_eq!(output, expected);
Ok(())
}
#[test]
fn test_summary_with_backticks() -> Result<()> {
let opts = create_test_opts(false, true); let fi1 = create_file_info("file with space.txt", None, false);
let fi2 = create_file_info("another.rs", None, false);
let files = vec![&fi1, &fi2];
let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (2)\n- `another.rs`\n- `file with space.txt`\n"; assert_eq!(output, expected);
Ok(())
}
#[test]
fn test_summary_with_counts_and_backticks() -> Result<()> {
let opts = create_test_opts(true, true); let counts1 = Some(FileCounts {
lines: 2,
characters: 15,
words: 3,
});
let fi1 = create_file_info("data.csv", counts1, false);
let files = vec![&fi1];
let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (1)\n- `data.csv` (L:2 C:15 W:3)\n";
assert_eq!(output, expected);
Ok(())
}
#[test]
fn test_summary_with_binary_counts() -> Result<()> {
let opts = create_test_opts(true, false); let counts_text = Some(FileCounts {
lines: 10,
characters: 100,
words: 20,
});
let counts_binary = Some(FileCounts {
lines: 0, characters: 256,
words: 0,
});
let fi_text = create_file_info("text.txt", counts_text, false);
let fi_binary = create_file_info("binary.bin", counts_binary, true); let files = vec![&fi_text, &fi_binary];
let mut writer = Cursor::new(Vec::new());
write_summary(&mut writer, &files, &opts)?;
let output = String::from_utf8(writer.into_inner())?;
let expected = "---\nProcessed Files: (2)\n- binary.bin (Binary C:256)\n- text.txt (L:10 C:100 W:20)\n";
assert_eq!(output, expected);
Ok(())
}
}