Skip to main content

topo_render/
compact.rs

1use std::io::Write;
2use topo_core::ScoredFile;
3
4/// Writes scored files in compact single-line format for hook injection.
5///
6/// Output format: `path (role, Ntok, score)`
7/// Example: `src/auth.rs (impl, 2494tok, 7.01)`
8pub struct CompactWriter;
9
10impl CompactWriter {
11    pub fn new() -> Self {
12        Self
13    }
14
15    /// Render scored files as compact single-line entries.
16    pub fn render(&self, files: &[ScoredFile]) -> String {
17        let mut buf = Vec::new();
18        self.write_to(&mut buf, files).expect("write to Vec failed");
19        String::from_utf8(buf).expect("compact output is valid UTF-8")
20    }
21
22    /// Write compact output to a writer.
23    pub fn write_to(&self, writer: &mut dyn Write, files: &[ScoredFile]) -> std::io::Result<()> {
24        for file in files {
25            writeln!(
26                writer,
27                "{} ({}, {}tok, {:.2})",
28                file.path,
29                file.role.as_str(),
30                file.tokens,
31                file.score,
32            )?;
33        }
34        Ok(())
35    }
36}
37
38impl Default for CompactWriter {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use topo_core::{FileRole, Language, ScoredFile, SignalBreakdown};
48
49    fn sample_files() -> Vec<ScoredFile> {
50        vec![
51            ScoredFile {
52                path: "src/auth.rs".to_string(),
53                score: 7.01,
54                signals: SignalBreakdown::default(),
55                tokens: 2494,
56                language: Language::Rust,
57                role: FileRole::Implementation,
58            },
59            ScoredFile {
60                path: "src/commands/init.rs".to_string(),
61                score: 6.92,
62                signals: SignalBreakdown::default(),
63                tokens: 2635,
64                language: Language::Rust,
65                role: FileRole::Implementation,
66            },
67            ScoredFile {
68                path: "README.md".to_string(),
69                score: 6.54,
70                signals: SignalBreakdown::default(),
71                tokens: 128,
72                language: Language::Markdown,
73                role: FileRole::Documentation,
74            },
75        ]
76    }
77
78    #[test]
79    fn compact_output_one_line_per_file() {
80        let writer = CompactWriter::new();
81        let output = writer.render(&sample_files());
82        let lines: Vec<&str> = output.trim().lines().collect();
83        assert_eq!(lines.len(), 3);
84    }
85
86    #[test]
87    fn compact_output_format() {
88        let writer = CompactWriter::new();
89        let output = writer.render(&sample_files());
90        let first_line = output.lines().next().unwrap();
91        assert_eq!(first_line, "src/auth.rs (impl, 2494tok, 7.01)");
92    }
93
94    #[test]
95    fn compact_output_includes_role() {
96        let writer = CompactWriter::new();
97        let output = writer.render(&sample_files());
98        assert!(output.contains("(docs,"));
99        assert!(output.contains("(impl,"));
100    }
101
102    #[test]
103    fn compact_empty_files() {
104        let writer = CompactWriter::new();
105        let output = writer.render(&[]);
106        assert!(output.is_empty());
107    }
108}